{"posts":[{"title":"Go语言Sync包的锁","content":"互斥锁 解锁之前加锁，会导致死锁 加锁之前解锁，也会导致死锁 读写互斥锁 读写互斥锁根据互斥锁得来 加了写锁后，不能再加读锁 加了读锁后，可以继续加读锁 加了读锁后，不能再加写锁，否则都会产生死锁 ","link":"https://hzqyihui.github.io/post/go-yu-yan-sync-bao-de-suo/"},{"title":"Nacos安装和配置：","content":"Docker安装： 安装Nacos 或者直接在mac 上安装 jdk maven 和 nacos 参考地址：https://nacos.io/zh-cn/docs/quick-start.html ","link":"https://hzqyihui.github.io/post/nacos-an-zhuang-he-pei-zhi/"},{"title":"Nacos基本使用：","content":" 命名空间 可以进行配置隔离，设定每个微服务一个命名空间。 将某些配置集放到某一命名空间 组 抛出一个问题，你现在确实能够隔离微服务，但是不同的微服务的开发，测试，生产环境如何区别。 组可以用来区别, 开发，测试，生产环境。 dataid - 配置集， 一个配置集就是一个配置文件，实际上可以更灵活。 测试使用： yaml转json的网址： http://json2yaml.com/ 如何将nacos 中的配置映射成Go的 struct //go语言本身支持将json的字符串反射成struct ","link":"https://hzqyihui.github.io/post/nacos-ji-ben-shi-yong/"},{"title":"布隆过滤器","content":"PHP代码实现： ","link":"https://hzqyihui.github.io/post/bu-long-guo-lu-qi/"},{"title":"brew换源：","content":"brew换源： 中科大： 阿里： 清华： 如果需要恢复原有镜像源的话（国内镜像源突然不能用了或版本不够新） 如果可以了下面的就不用看了 如果不行的话可以依次尝试以下命令 ","link":"https://hzqyihui.github.io/post/brew-huan-yuan/"},{"title":"jenkins的安装和配置：","content":"jenkins的安装和配置： 1. 部署中的痛点 为什么要用Jenkins? 我说下我以前开发的痛点，在一些中小型企业，每次开发一个项目完成后，需要打包部署，可能没有专门的运维人员，只能开发人员去把项目打成一个war包，可能这个项目已经上线了，需要把服务关，在部署到服务器上，将项目启动起来，这个时候可能某个用户正在操作某些功能上的东西，如果你隔三差五的部署一下，这样的话对用户的体验也不好，自己也是烦的很，总是打包拖到服务器上。希望小型企业工作人员学习一下，配置可能复杂，但是你配置好了之后，你只需要把代码提交到Git或者SVN上，自动构建部署，非常方便。 2. 什么是jenkins Jenkins是一个开源的、提供友好操作界面的持续集成 (CI) 工具，起源于Hudson(Hudson是商用的)，主要用于持续、自动的构建/测试软件项目、监控外部任务的运行（这个比较抽象，暂且写上，不做解释）。Jenkins用Java语言编写，可在Tomcat等流行的servlet容器中运行，也可独立运行。通常与版本管理工具(SCM)、构建工具结合使用。常用的版本控制工具有SVN、GlT,构建工具有Maven、Ant、Gradle。 3. jenkins的安装和配置 这个jenkins我们不会直接安装在192.168.0.104我们会在一个新的虚拟机上运行192.168.0.102，我们会在下一节 课中专门讲解 3.1 安装java 3.2 下载 jenkins 3.3 上传安装包并安装 3.4 修改 jenkins 配置 3.5 启动 jenkins 3.6 关闭防火墙 3.7 在浏览器中访问 输入http:/192.168.0.104:8088会看到下面页面 4. jenkins插件管理 jenkins本身功能简单，但是jenkins提供的插件机制使得jenkins可以开发很多插件来使得jenkins很强大。但jenkins插件默认从国外官方地址下载，速度会很慢，所以需要在安装插件前将下载地址改为国内的下载地址 4.1 修改插件下载地址 修改default,.json文件 进入Manage Jenkins-》Manage Plugin-&gt;Advanced最下面有Update Site设置为： 4.2 插件安装 ","link":"https://hzqyihui.github.io/post/jenkins-de-an-zhuang-he-pei-zhi/"},{"title":"Kong怎么和Consul配套使用？","content":"Kong怎么和Consul配套使用？ 只需要在Host里写上 goods-web.service.consul 的配置即可（黑色部分是注册到consul的服务的名字， 后面是写死的后缀）， 以上操作会把 host 和 port 都给覆盖， 也就集成了 API网关 从服务注册中心 拿地址的功能了。 并且配置后，如果 goods-web 是多个实例（集群的），那么 kong 会自动完成 负载均衡 的过程。 ","link":"https://hzqyihui.github.io/post/kong-zen-me-he-consul-pei-tao-shi-yong/"},{"title":"Kong的安装和配置：","content":"1. Kong的介绍： Kong是一个开源的API网关，它是一个针对API的一个管理工具。你可以在那些上游服务之前，额外地实现一些功能。 Kong本身是一款基于OpenResty(Nginx+Lua模块)编写的高可用、易扩展的，由Mashape公司开源的API GatewayI项目。Kong是基于NGINX和Apache Cassandra或PostgreSQLi构建的，能提供易于使用的RESTful API来操作和配置API管理系统，所以它可以水平扩展多个Kong服务器，通过前置的负载均衡衡配置把请求均匀地分发到各个Server,来应对大批量的网络请求。 services: Service顾名思义就是我自色是义的上游服务，通过Kong匹配到相应的请求要转发的地方，Service可以与下面的Routei进行关联，一个Service可以有很多Route,匹配到的Route就会转发到 学习链接：https://www.zhihu.com/topic/20174970/hot 2. 安装： 2.1 安装postgresql 和 migrations 2.2 配置信息 2.3 初始化数据库并启动 web地址： http://127.0.0.1:8001/ 8001:kong的管理的端口 8000:用户访问 1337:konga地址 启动中遇到遇到加载ssl文件的错误 2.4 安装konga（kong的可视化客户端） 3. 配置Jwt 通用认证 一般情况下，上游API服务都需要客户端有身份认证，且不允许错误的认证或无认证的请求通过。认证插件可以实现这一需求。这些插件的通用方案/流程如下： 向一个API或全局添加AUTH插件（此插件不作用于consumers); 创建一个consumer)对象； 为consumer提供指定的验证插件方案的身份验证凭据： 现在，只要有请求进入Kog,都将检查其提供的身份验证凭据（取决于auth类型），如果该请求无法被验证或者验证失败，则请求会被锁定，不执行向上游服务转发的操作。 但是，上述的一般流程并不是总是有效的。譬如，当使用了外部验证方案（比如LDAP)时，KONG就不会（不需 要)对consumer进行身份验证。 Consumers 最简单的理解和配置consumer的方式是，将其于用户进行一映射，即一个consumer代表一个用户（或应用）。但是对于KONG而言，这些都无所谓。consumer的核心原则是你可以为其添加插件，从而自定义他的请求行为：所以，或许你会有一个手机APP应用，并为他的每个版本都定义一个consumer,又或者你又一个应用或几个应用，并为这些应用定义统一个consumer,这些都无所谓。这是一个模糊的概念，他叫做consumer,而不是user！万万要区分开来，且不可混淆。 匿名验证 首先需要创建一个Service:来做上有服务，来匹配到相应的相应的转发的目的地，一个Service可以由多个Route, 匹配到的Route:都会转发给Service。Service可以是一个世纪的地址，也可以是kong内部提供的upstream object 配置过程 新建一个consumer 为这个consumer添加jwt 记住key! ! 配置全局的plugins 设置header为x-token 在jwt.io生成一个token 在payload中添加“iss”: &quot;imooc&quot; 注意：新建jwt的时候 key 必须和实际生成的 token 中的 payload 中的 iss 的值保持一致 请求的时候的 x-token 需要在 正常token前面 加一个 Bearer ","link":"https://hzqyihui.github.io/post/kong-de-an-zhuang-he-pei-zhi/"},{"title":"什么是API网关？","content":"1.什么是API网关？ 1.1 什么是API网关？ 动态路由 限流管理 1.2 API网关应该具备什么功能？ 服务的路由 动态路由 负载均衡 服务发现 限流 熔断、降级 流量管理 黑白名单 反爬处理 权限管理 1.3 API网关技术选型 go java nginx ","link":"https://hzqyihui.github.io/post/1shi-me-shi-api-wang-guan/"},{"title":"RocketMQ Go 消息使用：","content":"RocketMQ Go客户端使用： 1. 普通消息 1.1 生产者 https://github.com/apache/rocketmq-client-go/blob/master/examples/producer/simple/main.go 1.2 消费者 2. 延迟消息 2.1 生产者 3. 事务消息 3.1 生产者 不能随意shutdown，因为运行的 rocketmq 的 client 都是同一个，如果一个关了，其他所有的都关了。 ","link":"https://hzqyihui.github.io/post/rocketmq-go-xiao-xi-shi-yong/"},{"title":"服务雪崩","content":"服务雪崩： 形成原因： 服务雪崩形成可以分为三个阶段： 服务提供者不可用： 重试加大清求流量： 服务调用者不可用： 服务雪崩的每个阶段都可能由不同的原因造成，总结如下： 应对策略： 一开始你的服务能考虑抗住高并发吗？ 成本的增加：开发成本硬件成本-我要抗住高并发-10W-500 即使是淘宝这种平时的流量和双11的流量差异就很大 我们现在考虑到我今年可能出现一次高并发1W-我全年的服务都部署成可以抗住1W并发-500·1k 某个时候出现了流量的猛增·2k 限流 - 2k但是我的服务能力只有1k,所以这个时候多出来的流量怎么办：1. 拒绝 2. 排队等待工 用户体验不太好：当前访问用户过多，请稍后重试 和 你的服务直接挂了 用户体验降级了-原本是访问流畅，下单流畅 -&gt; 当前访问用户过多，请稍后重试 熔断 - 比如A服务访问B服务，这个时候B服务很慢 - B服务压力过大，导致了出现了不少请求错误，调用方很容易出现一个问题：每次调用都超时2k, 结果这个时候数据库出现了问题，超时重试-网络2k的流量突然变成了3k这让原本就满负荷的b服务雪上加霜，如果这个时候调用方有一种机制：比如说 1. 发现了大部分请求很慢-50% 请求都很慢，2. 发现我的请求有50%都错误了 3. 错误数量很多，比如1s出现了20个错误。 熔断 - 1.保险丝 2.股市熔断 ","link":"https://hzqyihui.github.io/post/fu-wu-xue-beng/"},{"title":"gRPC的超时和重试","content":"超时 timeout是为了保护服务，避免consumer服务因为provider响应慢而也变得响应很慢，这样consumer可以尽量保持原有的性能。 重试 如果provider只是偶尔抖动，那么超时后直接放弃，不做后续处理，就会导致当前请求错误，也会带来业务方面的损失。对于这种偶尔抖动，可以在超时后重试一下，重试如果正常返回了，那么这次请求就被挽救了，能够正常给前端返回数据，只不过比原来响应慢一点。重试可以考虑切换一台机器来进行调用，因为原来机器可能由于临时负载高而性能下降，重试会更加剧其性能问题，而换一台机器，得到更快返回的概率也更大一些。 幂等 如果允许consumer重试，那么provider就要能够做到幂等。同一个请求被consumer多次调用，对provider产生的影响是一致的。而且这个幂等应该是服务级别的，而不是某台机器层面的，重试调用任何一台机器，都应该做到幂等。 ","link":"https://hzqyihui.github.io/post/grpc-de-chao-shi-he-chong-shi/"},{"title":"常见的幂等性解决方案","content":"常见的幂等性解决方案 哪些情况下需要考虑幂等性-同样的请求发送多次： http请求的类型： get 获取商品信息，这个会引起商品的数据的变化吗？ post 比较常见，这种接口需要考虑到幂等性 put 不一定要实现幂等性 put把1号商品的价格改为200，网络返回的时候抖动了，重试 第二次接口还是会把1号商品的价格改为200-这种情况下没有幂等的问题 出现幂等性问题的情况： 购物车中的商品，调用一次这个商品的数量加一 第一次调用原本的值10之后价格变为11-但是返回的时候出现了网络抖动 第二次发送原本的值11之后价格变为12-但是返回的时候出现了网络抖动 第三次发送原本的值12之后价格变为13引但是返回的时候出现了网络抖动 delete 一般不具备幂等性的要求 第一次调用删除数据 第二次调用还是删除当前的数据 解决方案： 1. 唯一索引，防止新增脏数据 比如：新建用户的时候将手机号码设置为唯一索引，那么即使你重试，也只会新建一个用户，不会因为重试导致当前用户注册了两个用户。 要点： 唯一索引或唯一组合索引来防止新增数据存在脏数据 (当表存在唯一索引，并发时新增报错时，再查询一次就可以了，数据应该已经存在了，返回结果即可) 2. token机制，防止页面重复提交 业务要求： 页面的数据只能被点击提交一次 发生原因： 由于重复点击或者网络重发，或者nginx重发等情况会导致数据被重复提交 解决办法： 集群环境：采用token加redis(redis单线程的，处理需要排队) 处理流程： 数据提交前要向服务的申请token,token放到redis或内存，token有效时间 提交后后台校验token,同时删除token,生成新的tokeni返回 token特点： 要申请，一次有效性，可以限流 注意：redis要用删除操作来判断token,删除成功代表token校验通过，如果用select + delete来校验token,存在并发问题，不建议使用 3. 悲观锁 获取数据的时候加锁获取 注意：id字段一定是主键或者唯一索引，不然是锁表，会死人的 悲观锁使用时一般伴随事务一起使用，数据锁定时间可能会很长，根据实际情况选用 4. 乐观锁 乐观锁只是在更新数据那一刻锁表，其他时间不锁表，所以相对于悲观锁，效率更高。 乐观锁的实现方式多种多样可以通过version或者其他状态条件： 1.通过版本号实现 2.通过条件限制 要求：quality-#subQuality#&gt;=,，这个情景适合不用版本号，只更新是做数据安全校验，适合库存模型，扣份额和回滚份额，性能更高 注意：乐观锁的更新操作，最好用主键或者唯一索引来更新，这样是行锁，否则更新时会锁表，上面两个s改成下 面的两个更好 5. 分布式锁 还是拿插入数据的例子，如果是分布是系统，构建全局唯一索引比较困难，例如唯一性的字段没法确定，这 时候可以引入分布式锁，通过第三方的系统(redis或zookeeper),在业务系统插入数据或者更新数据，获取分布式 锁，然后做操作，之后释放锁，这样其实是把多线程并发的锁的思路，引入多多个系统，也就是分布式系统中得 解决思路。 要点：某个长流程处理过程要求不能并发执行，可以在流程执行之前根据某个标志（用户ID+后缀等）获取分布式锁，其他流程执行时获取锁就会失败，也就是同一时间该流程只能有一个能执行成功，执行完成后，释放分布式锁（分布式锁要第三方系统提供) 6. select + insert 并发不高的后台系统，或者一些任务JOB,为了支持幂等，支持重复执行，简单的处理方法是，先查询下一些关键数据，判断是否已经执行过，在进行业务处理，就可以了 注意：核心高并发流程不要用这种方法 7. 对外提供接口的api如何保证幂等 如银联提供的付款接口：需要接入商户提交付款请求时附带：source来源，seq序列号source+seq在数据库里面做唯一索引，防止多次付款，（并发时，只能处理一个请求） 重点： 对外提供接口为了支持幂等调用，接口有两个字段必须传，一个是来源source,一个是来源方序列号seq,这个两个字段在提供方系统里面做联合唯一索引，这样当第三方调用时，先在本方系统里面查询一下，是否已经处理过，返回相应处理结果；没有处理过，进行相应处理过，返回结果。注意，为了幂等友好，一定要先查询一下，是否处理过该笔业务，不查询直接插入业务系统，会报错，但实际已经处理了。 总结： 幂等与你是不是分布式高并发没有关系。关键是你的操作是不是幂等的。一个幂等的操作典型如：把编号为5的记录的A字段设置为0这种操作不管执行多少次都是幂等的。一个非幂等的操作典型如：把编号为5的记录的A字段增加1这种操作显然就不是幂等的。要做到幂等性，从接口设计上来说不设计任何非幂等的操作即可。譬如说需求是：当用户点击赞同时，将答案的赞同数量+1。改为：当用户点击赞同时，确保答案赞同表中存在一条记录，用户、答案。赞同数量由答案赞同表统计出来。总之幂等性应该是合格程序员的一个基因，在设计系统时，是首要考虑的问题，尤其是在像支付宝，银行，互联网金融公司等涉及的都是钱的系统，既要高效，数据也要准确，所以不能出现多扣款，多打款等问题，这样会很难处理，用户体验也不好。 ","link":"https://hzqyihui.github.io/post/chang-jian-de-mi-deng-xing-jie-jue-fang-an/"},{"title":"Rocket基本概念：","content":"2. Rocket基本概念 Producer:消息的发送者；举例：发信者 Consumer:消息接收者；举例：收信者 Broker:暂存和传输消息；举例：邮局 NameServer:管理Broker;举例：各个邮局的管理机构 Topic:区分消息的种类；一个发送者可以发送消息给一个或者多个Topic;一个消息的接收者可以订阅一个或者多个Topici消息 Message Queue:相当于是Topic的分区；用于并行发送和接收消息 3. Rocket的消息类型 3.1 按照发送的特点分： 3.1.1 同步发送 a.同步发送，线程阻塞，投递completes阻塞结束 b.如果发送失败，会在默认的超时时间3秒内进行重试，最多重试2次 c.投递completes不代表投递成功，要check SendResult.sendStatus来判断是否投递成功 d.SendResult!里面有发送状态的枚举：SendStatus,同步的消息投递有一个状态返回值的 e.retry的实现原理：只有ack的SendStatus=SEND_OK才会停止retry 注意事项：发送同步消息且Ack为SEND OK,只代表该消息成功的写入了MQ当中，并不代表该消息成功的被Consumeri消费了。 3.1.2 异步发送 a.异步调用的话，当前线程一定要等待异步线程回调结束再关闭producer啊，因为是异步的，不会阻塞，提前关闭producer会导致未回调链接就断开了 b.异步消息不retry,投递失败回调onException(0方法，只有同步消息才会retry,源码参考DefaultMQProducerlmpl.class C.异步发送一般用于链路耗时较长，对T响应时间较为敏感的业务场景，例如用户视频上传后通知启动转码服务，转码完成后通知推送转码结果等。 3.1.3 单向发送 a.消息不可靠，性能高，只负责往服务器发送一条消息，不会重试也不关心是否发送成功 b.此方式发送消息的过程耗时非常短，一般在微秒级别 3.2 按照发送的特点分： 3.2.1 普通消息（订阅） 普通消息是我们在业务开发中用到的最多的消息类型，生产者需要关注消息发送成功即可，消费者消费到消息即可，不需要保证消息的顺序，所以消息可以大规模并发地发送和消费，吞吐量很高，适合大部分场景。 3.2.2 顺序消息 顺序消息分为分区顺序消息和全局顺序消息，全局顺序消息比较容易理解，也就是哪条消息先进入，哪条消息就会先被消费，符合我们的FIFO,很多时候全局消息的实现代价很大，所以就出现了分区顺序消息。分区顺序消息的概念可以如下图所示： 3.2.3 延时消息 - 作用：订单超时库存归还 延迟的机制是在服务端实现的，也就是 Broker 收到了消息，但是经过一段时间以后才发送服务器按照1-N定义了如下级别：“1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h”;若要发送定时消息，在应用层初始化Messagei消息对象之后，调用Message.setDelayTimeLevel(int level)方法来设置延迟级别，按照序列取相应的延迟级别，例如level=2,则延迟为5s 实现原理： a.发送消息的时候如果消息设置了DelayTimeLevel,那么该消息会被丢到 ScheduleMessageService.SCHEDULE_TOPIC这个Topic.里面 b.根据DelayTimeLeveli选择对应的queue c.再把真实的topici和queue信息封装起来，set到msg里面 d.然后每个SCHEDULE_TOPIC_XXXX的每个DelayTimeLevelQueue,有定时任务去刷新6i是否有待投递的消息 e.每10s定时持久化发送进度 3.2.4 事务消息 https://help.aliyun.com/document_detail/43348.html?spm=a2c4g.11186623.2.16.78ee6192siK1qV#concept-2047067 消息队列RocketMQ版提供的分布式事务消息适用于所有对数据最终一致性有强需求的场景。本文介绍消息队列 RocketMQ版事务消息的概念、优势、典型场景、交互流程以及使用过程中的注意事项。 概念介绍 事务消息：消息队列RocketMQ版提供类似X或Open XA的分布式事务功能，通过消息队列RocketMQ版事务消息能达到分布式事务的最终一致。 半事务消息：暂不能投递的消息，发送方已经成功地将消息发送到了消息队列RocketMQ版服务端，但是服务端未收到生产者对该消息的二次确认，此时该消息被标记成“暂不能投递”状态，处于该种状态下的消息即半事务消息。 消息回查：由于网络闪断、生产者应用重启等原因，导致某条事务消息的二次确认丢失，消息队列RocketMQ版服务端通过扫描发现某条消息长期处于“半事务消息”时，需要主动向消息生产者询问该消息的最终状态(Commit或是Rollback),该询问过程即消息回查。 分布式事务消息的优势 消息队列RocketMQ版分布式事务消息不仅可以实现应用之间的解耦，又能保证数据的最终一致性。同时，传统的大事务可以被拆分为小事务，不仅能提升效率，还不会因为某一个关联应用的不可用导致整体回滚，从而最大限度保证核心系统的可用性。在极端情况下，如果关联的某一个应用始终无法处理成功，也只需对当前应用进行补偿或数据订正处理，而无需对整体业务进行回滚。 ","link":"https://hzqyihui.github.io/post/rocket-ji-ben-gai-nian/"},{"title":"Rocket安装和配置","content":"1. 单独进行Docker安装 1.1 server 有日志目录映射 1.2 broker 目录映射 注意 如果你的微服务或者项目在开发的时候没有放入 docker中或者与rocketmq容器不能直接用IP访问， 那么请把broker.conf中的 1.3 console broker 配置文件 2. 官网二进制包安装 2.1 安装RocketMQ 2.2 安装RocketMQ-dashboard （数据看板） 2.3 启动NameServer​ 安装完RocketMQ包后，我们启动NameServer 2.4 启动Broker NameServer成功启动后，我们启动Broker 2.5 消息收发(测试) 在进行消息收发之前，我们需要告诉客户端NameServer的地址，RocketMQ有多种方式在客户端中设置NameServer地址，这里我们利用环境变量 NAMESRV_ADDR 2.6 关闭服务器 ","link":"https://hzqyihui.github.io/post/rocket-an-zhuang-he-pei-zhi/"},{"title":"常见分布式事务解决方案：","content":"常见分布式事务解决方案 两阶段提交(2PC,Two-phase Commit) TCC补偿模式 基于本地消息表实现最终一致性 最大努力通知 基于可靠消息最终一致性方案 1、两阶段提交(2PC) 两阶段提交又称2PC,2PC是一个非常经典的中心化的原子提交协议。 这里所说的中心化是指协议中有两类节点：一个是中心化协调者节点(coordinator)和N个参与者节点(partcipant)。 两个阶段：第一阶段：投票阶段 和第二阶段：提交/执行阶段。 举例 订单服务A,需要调用 支付服务B 去支付，支付成功则处理购物订单为待发货状态，否则就需要将购物订单处 理为失败状态。 那么看2PC阶段是如何处理的 1.1、第一阶段：投票阶段 第一阶段主要分为3步 事务询问 协调者向所有的参与者发送事务预处理请求，称之为Prepare,并开始等待各参与者的响应。 执行本地事务 各个参与者节点执行本地事务操作，但在执行完成后并不会真正提交数据库本地事务，而是先向协调者报告说：“我这边可以处理了/我这边不能处理”。· 各参与者向协调者反馈事务询的响应 如果参与者成功执行了事务操作，那么就反馈给协调者Ys响应，表示事务可以执行，如果没有参与者成功执行事务，那么就反馈给协调者N。响应，表示事务不可以执行。 第一阶段执行完后，会有两种可能。1、所有都返回Ys.2、有一个或者多个返回No. 1.2、第二阶段：提交/执行阶段（成功流程） 成功条件：所有参与者都返回Yes。 第二阶段主要分为两步 所有的参与者反馈给协调者的信息都是Yes,那么就会执行事务提交 协调者向所有参与者节点发出Commit请求. 事务提交 参与者收到Commiti请求之后，就会正式执行本地事务Commit操作，并在完成提交之后释放整个事务执行期间占用的事务资源。 1.3、第二阶段：提交/执行阶段（异常流程） 异常条件：任何一个参与者向协调者反馈了N。响应，或者等待超时之后，协调者尚未收到所有参与者的反馈响应。 异常流程第二阶段也分为两步 发送回滚请求 协调者向所有参与者节点发出RoollBack请求 事务回滚 参与者接收到RoollBack清求后，会回滚本地事务。 1.4、2PC缺点 通过上面的演示，很容易想到2pc所带来的缺陷 性能问题 无论是在第一阶段的过程中，还是在第二阶段，所有的参与者资源和协调者资源都是被锁住的，只有当所有节点准备完毕，事务协调者才会通知进行全局提交，参与者进行本地事务提交后才会释放资源。这样的过程会比较漫长，对性能影响比较大。 单节点故障 由于协调者的重要性，一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段，协调者发生故障，那么所有的参与者还都处于锁定事务资源的状态中，而无法继续完成事务操作。（虽然协调者挂掉，可以重新选举一个协调者，但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题) 2PC出现单点问题的三种情况 协调者正常，参与者宕机 由于协调者无法收集到所有参与者的反馈，会陷入阻塞情况。 解决方案：引入超时机制，如果协调者在超过指定的时间还没有收到参与者的反馈，事务就失败，向所有节点发送终止事务请求 协调者宕机，参与者正常 无论处于哪个阶段，由于协调者宕机，无法发送提交请求，所有处于执行了操作但是未提交状态的参与者都会陷入 阻塞情况 解决方案：引入协调者备份，同时协调者需记录操作日志.当检测到协调者宕机一段时间后，协调者备份取代协调者，并读取操作日志，向所有参与者询问状态。 - 协调者和参与者都宕机 1. 发生在第一阶段：因为第一阶段，所有参与者都没有真正执行commit,所以只需重新在剩余的参与者中重新选出一个协调者，新的协调者在重新执行第一阶段和第二阶段就可以了。 2. 发生在第二阶段并且挂了的参与者在挂掉之前没有收到协调者的指令。也就是上面的第4步挂了，这是可能协调者 还没有发送第4步就挂了。这种情形下，新的协调者重新执行第一阶段和第二阶段操作。 3. 发生在第二阶段并且有部分参与者已经执行完commit操作。就好比这里订单服务A和支付服务B都收到协调者发送的commitf信息，开始真正执行本地事务commit,.但突发情况，A commit成功，B确挂了。这个时候目前来讲数据时不一致的。 2、TCC分布式事务（名字含义： Try、Confirm 或 Cancel） 一个订单支付之后，我们需要做下面的步骤： 更改订单的状态为“已支付” 扣减商品库存 给会员增加积分 创建销售出库单通知仓库发货 好，业务场景有了，现在我们要更进一步，实现一个TCC分布式事务的效果。 什么意思呢？也就是说： [1]订单服务-修改订单状态 [2]库存服务-扣减库存 [3]积分服务-增加积分 [4]仓储服务-创建销售出库单。 上述这几个步骤，要么一起成功，要么一起失败，必须是一个整体性的事务。 举个例子，现在订单的状态都修改为“已支付”了，结果库存服务扣减库存失败。那个商品的库存原来是100 件，现在卖掉了2件，本来应该是98件了。结果呢？由于库存服务操作数据库异常，导致库存数量还是100。这不是在坑人么，当然不能允许这种情况发生了！ 但是如果你不用TCC分布式事务方案的话，就用个go开发这么一个微服务系统，很有可能会干出这种事儿来。 我们来看看下面的这个图，直观的表达了上述的过程： 所以说，我们有必要使用TCC分布式事务机制来保证各个服务形成一个整体性的事务。 上面那几个步骤，要么全部成功，如果任何一个服务的操作失败了，就全部一起回滚，撤销已经完成的操作。 比如说库存服务要是扣减库存失败了，那么订单服务就得撤销那个修改订单状态的操作，然后得停止执行增加积分和通知出库两个操作。 说了那么多，老规矩，给大家上一张图，大伙儿顺着图来直观的感受一下： 落地实现TCC分布式事务 那么现在到底要如何来实现一个TCC分布式事务，使得各个服务，要么一起成功？要么一起失败呢？大家稍安勿躁，我们这就来一步一步的分析一下。咱们就以一个go开发系统作为背景来解释。 TCC实现阶段一：Try 首先，订单服务那儿，他的代码大致来说应该是这样 总结与思考 总结一下，你要玩儿TCC分布式事务的话： 首先需要选择某种TCC分布式事务框架，各个服务里就会有这个TCC分布式事务框架在运行。 然后你原本的一个接口，要改造为3个逻辑，Try-Confirm-Cancel。 先是服务调用链路依次执行Try逻辑 如果都正常的话，TCC分布式事务框架推进执行Confirm逻辑，完成整个事务 如果某个服务的Try逻辑有问题，TCC分布式事务框架感知到之后就会推进执行各个服务的Cancel逻辑，撤销之前执行的各种操作。 这就是所谓的TCC分布式事务。 TCC分布式事务的核心思想，说白了，就是当遇到下面这些情况时， 某个服务的数据库宕机了 某个服务自己挂了 那个服务的redis、elasticsearch、MQ等基础设施故障了 某些资源不足了，比如说库存不够这些 先来Ty一下，不要把业务逻辑完成，先试试看，看各个服务能不能基本正常运转，能不能先冻结我需要的资源。 如果Try都ok,也就是说，底层的数据库、redis、elasticsearch、MQ都是可以写入数据的，并且你保留好了需要使用的一些资源（比如冻结了一部分库存）。 接着，再执行各个服务的Confirm逻辑，基本上Confirm就可以很大概率保证一个分布式事务的完成了。 那如果Try阶段某个服务就失败了，比如说底层的数据库挂了，或者redis挂了，等等。 此时就自动执行各个服务的Canceli逻辑，把之前的Try逻辑都回滚，所有服务都不要执行任何设计的业务逻辑。保证大家要么一起成功，要么一起失败。 终极大招 如果有一些意外的情况发生了，比如说订单服务突然挂了，然后再次重启，TCC分布式事务框架是如何保证之前没执行完的分布式事务继续执行的呢？ -- TCC事务管理器，事务日志 TCC事务框架都是要记录一些分布式事务的活动日志的，可以在磁盘上的日志文件里记录，也可以在数据库里记录。保存下来分布式事务运行的各个阶段和状态。 万一某个服务的Cancel或者Confirm逻辑执行一直失败怎么办呢？ 那也很简单，TCC事务框架会通过活动日志记录各个服务的状态。 举个例子，比如发现某个服务的Cancel或者CΦnfirm一直没成功，会不停的重试调用他的Cancel或者Confirmi逻辑，务必要他成功！ 当然了，如果你的代码没有写什么bug,有充足的测试，而且Ty阶段都基本尝试了一下，那么其实一般Confirm、Cancel都是可以成功的！ 如果实在解决不了，那么这个一定是很小概率的事件，这个时候发邮件通知人工处理 seata(阿里java)， go-seata， 现在的开源TCC框架 TCC优缺点 优点： 解决了跨服务的业务操作原子性问题，例如组合支付，订单减库存等场景非常实用 TCC的本质原理是把数据库的二阶段提交上升到微服务来实现，从而避免了数据库2阶段中锁冲突的长事务低性能风险。 TCC异步高性能，它采用了try先检查，然后异步实现confirm,真正提交的是在confirm方法中。 缺点： 对微服务的侵入性强，微服务的每个事务都必须实现try,confirm,cancel等3个方法，开发成本高，今后维护改造的成本也高。 为了达到事务的一致性要求，try,confirm、cancel接口必须实现等幂性操作。(定时器+重试) 由于事务管理器要记录事务日志，必定会损耗一定的性能，并使得整个TCC事务时间拉长，建议采用redis的方式来记录事务日志。 tcc需要通过锁来确保数据的一致性，会加锁导致性能不高 3. 基于本地消息表的最终一致性 本地消息表方案 本地消息表这个方案最初是eBay提出的，此方案的核心是通过本地事务保证数据业务操作和消息的一致性，然后通过定时任务将消息发送至消息中间件，待确认消息发送给消费方成功再将消息删除。 下面以注册送积分为例来说明： 下例共有两个微服务交互，用户服务和积分服务，用户服务负责添加用户，积分服务负责增加积分。 3.1、用户注册 用户服务在本地事务新增用户和增加“积分消息日志”。（用户表和消息表通过本地事务保证一致） 下表是伪代码 这种情况下，本地数据库操作与存储积分消息日志处于同一事务中，本地数据库操作与记录消息日志操作具备原子 性。 3.2、定时任务扫描日志 如何保证将消息发送给消息队列呢？ 经过第一步消息已经写到消息日志表中，可以启动独立的线程，定时对消息日志表中的消息进行扫描并发送至消息中间件，在消息中间件反馈发送成功后删除该消息日志，否则等待定时任务下一周期重试。 3.3、消费消息 如何保证消费者一定能消费到消息呢？ 这里可以使用MQ的ack(即消息确认)机制，消费者监听MQ,如果消费者接收到消息并且业务处理完成后向MQ发送ack(即消息确认)，此时说明消费者正常消费消息完成，MQ将不再向消费者推送消息，否则消费者会不断重试向消费者来发送消息。 积分服务接收到“增加积分”消息，开始增加积分，积分增加成功后消息中间件回应ack,否则消息中间件将重复投递此消息。 由于消息会重复投递，积分服务的“增加积分”功能需要实现幂等性。 总结：上诉的方式是一种非常经典的实现，基本避免了分布式事务，实现了“最终一致性”。但是，关系型数据库的吞吐量和性能方面存在瓶颈，频繁的读写消息会给数据库造成压力。所以，在真正的高并发场景下，该方案也会有瓶颈和限制的。 4. 基于可靠消息的最终一致性 （较为流行的方案） RabbitMQ 和 RocketMQ 都支持半消息机制， 但是 RocketMQ更关键的一点是支持事务消息回查。 5. 最大努力通知方案 某些特殊系统需要的方式：如支付宝支付 ","link":"https://hzqyihui.github.io/post/chang-jian-fen-bu-shi-shi-wu-jie-jue-fang-an/"},{"title":"CAP和BASE理论：","content":"CAP和BASE理论： 1.CAP理论 cap理论是分布式系统的理论基石 Consistency(一致性)： “all nodes see the same data at the same time”,即更新操作成功并返回客户端后，所有节点在同一时间的数据完全一致，这就是分布式的一致性。一致性的问题在并发系统中不可避免，对于客户端来说，一致性指的是并发访问时更新过的数据如何获取的问题。从服务端来看，则是更新如何复制分布到整个系统，以保证数据最终一致。 Availability(可用性)： 可用性指“Reads and writes always succeed”,即服务一直可用，而且是正常响应时间。好的可用性主要是指系统能够很好的为用户服务，不出现用户操作失败或者访问超时等用户体验不好的情况。 Partition Tolerance(分区容错性)： 即分布式系统在遇到某节点或网络分区故障的时候，仍然能够对外提供满足一致性和可用性的服务。分区容错性要求能够使应用虽然是一个分布式系统，而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了，其他剩下的机器还能够正常运转满足系统需求，对于用户而言并没有什么体验上的影响。 如果你是一个分布式系统，那么必须满足一点：分区容错性 二、取舍策略 CAP三个特性只能满足其中两个，那么取舍的策略就共有三种： AP：No SQL、Coach DB、Cassandra、DynamoDB CA without P：如果不要求P(不允许分区)，则C(强一致性)和A(可用性)是可以保证的。但放弃P的同时也就意味着放弃了系统的扩展性，也就是分布式节点受限，没办法部署子节点，这是违背分布式系统设计的初衷的。 CP without A：如果不要求A(可用)，相当于每个请求都需要在服务器之间保持强一致，而P(分区)会导致同步时间无限延长（也就是等待数据同步完才能正常访问服务），一旦发生网络故障或者消息丢失等情况，就要牺牲用户的体验，等待所有数据全部一致了之后再让用户访问系统。设计成CP的系统其实不少，最典型的就是分布式数据库，如Redis、HBase等。对于这些分布式数据库来说，数据的一致性是最基本的要求，因为如果连这个标准都达不到，那么直接采用关系型数据库就好，没必要再浪费资源来部署分布式数据库。 AP wihtout C: 要高可用并允许分区，则需放弃一致性。一旦分区发生，节点之间可能会失去联系，为了高可用，每个节点只能用本地数据提供服务，而这样会导致全局数据的不一致性。典型的应用就如某米的抢购手机场景，可能前几秒你浏览商品的时候页面提示是有库存的，当你选择完商品准备下单的时候，系统提示你下单失败，商品已售完。这其实就是先在A(可用性)方面保证系统可以正常的服务，然后在数据的一致性方面做了些牺牲，虽然多少会影响一些用户体验，但也不至于造成用户购物流程的严重阻塞。 2. BASE理论 分布式系统中的 一致性是 弱一致性， 但数据库MySQL的一致性 强一致性 BASE是 **Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性) **三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果，其来源于对大规模互联网系统分布式实践的总结，是基于CAP定理逐步演化而来的。BASE理论的核心思想是：即使无法做到强一致性，但每个应用都可以根据自身业务特点，采用适当的方式来使系统达到最终一致性。接下来看一下BASE中的三要素： 1、基本可用 基本可用是指分布式系统在出现不可预知故障的时候，允许损失部分可用性一-注意，这绝不等价于系统不可用。比 如： (1)响应时间上的损失。正常情况下，一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果，但由于出现故障，查询结果的响应时间增加了1~2秒 (2)系统性能上的损失：正常情况下，在一个电子商务网站上进行购物的时候，消费者几乎能够顺利完成每一笔订单，但是在一些节日大促购物高峰的时候，由于消费者的购物行为激增，为了保护购物系统的稳定性，部分消费者可能会被引导到一个降级页面 2、软状态 软状态指允许系统中的数据存在中间状态，并认为该中间状态的存在不会影响系统的整体可用性，即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时 3、最终一致性 最终一致性强调的是所有的数据副本，在经过一段时间的同步之后，最终都能够达到一个一致的状态。因此，最终一致性的本质是需要系统保证最终数据能够达到一致，而不需要实时保证系统数据的强一致性。总的来说，BASE理论面向的是大型高可用可扩展的分布式系统，和传统的事物ACID特性是相反的，它完全不同于ACID的强一致性模型，而是通过牺牲强一致性来获得可用性，并允许数据在一段时间内是不一致的，但最终达到一致状态。但同时，在实际的分布式场景中，不同业务单元和组件对数据一致性的要求是不同的，因此在具体的分布式系统架构设计过程中，ACID特性和BASE理论往往又会结合在一起。 一句话：CAP就是告诉你：想要满足C、A、P就是做梦，BASE才是你最终的归宿 ","link":"https://hzqyihui.github.io/post/cap-he-base-li-lun/"},{"title":"事务和分布式事务：","content":"1、事务概念： 一组sq语句操作单元，组内所有SQL语句完成一个业务，如果整组成功：意味着全部SQL都实现：如果其中任何一个失败，意味着整个操作都失败。失败，意味着整个过程都是没有意义的。应该是数据库回到操作前的初始状态。这种特性，就叫“事务”。 2、为什么要存在事务？ 失败后，可以回到开始位置 没都成功之前，别的用户（进程，会话）是不能看到操作内的数据修改的 3、事务4大特征ACID: 1. 原子性[atomicity] 功能不可再分，要么全部成功，要么全部失败 2. 一致性[consistency] 一致性是指数据处于一种语义上的有意义且正确的状态。一致性是对数据可见性的约束，保证在一个事务中的多次操作的数据中间状态对其他事务不可见的。因为这些中间状态，是一个过渡状态，与事务的开始状态和事务的结束状态是不一致的。 举个例子，张三给李四转账100元。事务要做的是从张三账户上减掉100元，李四账户上加上100元。 一致性的含义是其他事务要么看到张三还没有给李四转账的状态，要么张三已经成功转账给李四的状态， 而对于张三少了100元，李四还没加上100元这个中间状态是不可见的。 我们来看一下转账过程中可能存在的状态： 上述过程中：1.是初始状态、2是中间状态、3是最终状态，1和3是我们期待的状态，但是2这种状态却不是 我们期待出现的状态。 - 锁 那么反驳的声音来了： 要么转账操作全部成功，要么全部失败，这是原子性。从例子上看全部成功，那么一致性就是原子性的一部分咯，为什么还要单独说一致性和原子性？ 你说的不对。在未提交读的隔离级别下是事务内部操作是可见的，明显违背了一致性，怎么解释？ 好吧，需要注意的是： 原子性和一致性的的侧重点不同：原子性关注状态，要么全部成功，要么全部失败，不存在部分成功的状态。而一致性关注数据的可见性，中间状态的数据对外部不可见，只有最初状态和最终状态的数据对外可见 3. 隔离性[isolation] 事务的隔离性是指多个用户并发访问数据库时，一个用户的事务不能被其它用户的事务所干扰， 多个并发事务之间数据要相互隔离。 隔离性是多个事物的时候，相互不能干扰，一致性是要保证操作前和操作后数据或者数据结构的一致性，而我提到的事务的一致性是关注数据的中间状态，也就是一致性需要监视中间状态的数据，如果有变化，即刻回滚 如果不考虑隔离性，事务存在3种并发访问数据问题，也就是事务里面的脏读、不可重复读、虚度/幻读 mysql隔离级别，读未提交，读已提交，可重复读，串行化 4. 持久性[durablity] 是事务的保证，是事务终结的标志（内存的数据持久到硬盘文件中），或者有另一种说法，保持和现实世界一样，修改完成数据后，无论过去多长时间，该改变都是一直存在的。不会有变回去的说法。 4、分布式事务 分布式事务顾名思义就是要在分布式系统中实现事务，它其实是由多个本地事务组合而成。 对于分布式事务而言几乎满足不了ACID,其实对于单机事务而言大部分情况下也没有满足ACID,不然怎么会有四种隔离级别呢？所以更别说分布在不同数据库或者不同应用上的分布式事务了。 ","link":"https://hzqyihui.github.io/post/shi-wu-he-fen-bu-shi-shi-wu/"},{"title":"ElasticSearch学习：","content":"2.elasticsearch安装 2.1.关闭并禁用防火墙 2.2.通过docker安装elasticsearch Shell 2.3.通过docker安装kibana kibana 和 elasticsearch 的版本号应该保持一致 3. ES中的 type index, mapping he dsl 在 es 7里面， index 可以理解为 MySQL 的 table 了， 在7之前是 database，index 即是 table， 在 es 8之后 type就取消了 索引 有两个含义： 动词（insert），名词（表） Elasticsearch将它的数据存储到一个或者多个索引(index)中，索引就像数据库，可以向索引写入文档或者从索引中读取文档。 文档 文档(document)是Elasticsearch中的主要实体。所以，所有使用Elasticsearchi最终都会归结到文档的搜索上从客户端看，文档就是一个JSON对象，文档由字段构成，每个字段包含字段名以及一个或多个字段值。文档之间可能有各自不同的字段集合，文档没有固定的模式或强制的结构。 类型(7.x开始取消) Elasticsearch中每个文档都有与之对应的类型(type)定义，允许在一个索引存储多种文档类型，并提供不同映射。类型就像一个数据库表 映射映射做的就是，存储分析链所需的信息。主要就是设置一些参数，根据这些参数来做过滤还是分割词条。 4. 新建数据 在kibana的 console中写命令，就相当于是在执行HTTP命令，比如 GET _cat/indices 类似于执行http请求 4.1.通过put+id新建数据 在customer下保存id为1的数据，这里id是必须的 结果： 4.2. 发送post不带id新建数据 POST操作，带了id后，就和PUT带ID的作用（更新）一样了，PUT是不允许不带ID的。 POST和PUT的更新是全量更新，传的值会整体覆盖以前的数据。 4.3 post create 没有就创建，有就报错 查看index 5. 获取数据 5.1 获取数据 只返回source的值 5.2 搜索数据 ES有两种查询方式 5.2.1 通过urL查询数据 请求参数位于_search端点之后，参数之间使用&amp;分割，例如： 5.2.2通过request body查询数据 (平时经常用到，且重要) 6. 更新和删除数据 6.1 想要给已有的数据新增字段 #什么都不做 6.2 删除数据和索引 7. 批量插入和批量查询 7.1 批量插入（bulk） 7.2 批量获取 8. query dsl 查询 8.1 一个完整的查询语句应该 如何写？ 以下为官方参考文档 https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html 8.2 全文查询的方法 - 分词，倒排索引 8.2.1 match查询（匹配查询） match: 模糊匹配，需要指定字段名，但是输入会进行分词，比如&quot;hello world&quot;会进行拆分为hello和world, 然后匹配，如果字段中包含hello或者world,或者都包含的结果都会被查询出来，也就是说match是一个部分匹配的模糊查询。查询条件相对来说比较宽松。 8.2.2 match_phrase查询（短语查询） https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase.html match_phase： 会对输入做分词，但是需要结果中也包含所有的分词，而且顺序要求一样。以&quot;hello world&quot;为例，要求结果中必须包含hello和world,而且还要求他们是连着的，顺序也是固定的，hello that word不满足，world hello也不满足条件。 8.2.3.multi match查询 multi_match： 查询提供了一个简便的方法用来对多个字段执行相同的查询，即对指定的多个字段进行match查询 8.2.4 query_string查询 query_string: 和 match 类似，但是match需要指定字段名，query_string是在所有字段中搜索，范围更广泛。 8.3 match_all查询 8.4 term级别查询 8.4.1 term查询 （和match查询不同，match会分词） term: 这种查询和match在有些时候是等价的，比如我们查询单个的词hello,那么会和matchi查询结果一样，但是如果查询&quot;hello world&quot;,结果就相差很大，因为这个输入不会进行分词，就是说查询的时候，是查询字段分词结果中是否有&quot;hello world&quot;的字样，而不是查询字段中包含&quot;hello world&quot;的字样，elasticsearch会对字段内容进行分词，&quot;hello world&quot;会被分成hello和world,不存在&quot;hello world&quot;,因此这里的查询结果会为空。这也是term查询和match的区别。 8.4.2 range查询 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html 8.4.3 exist查询 查出有字段user的数据 查出没有有字段user的数据 8.4.4 模糊查询 8.5 复合查询 Elasticsearch bool查询对应Lucene BooleanQuery,格式如下 bool查询采用了一种匹配越多越好的方法，因此每个匹配的must或should子句的分数将被加在一起，以提供每个文档的最终得分 9. mapping 9.1 什么是Mapping? https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html 在一篇文章带你搞定ElasticSearch术语中，我们讲到了Mapping类似于数据库中的表结构定义schema,它 有以下几个作用： 定义索引中的字段的名称定义字段的数据类型，比如字符串、数字、布尔字段，倒排 索引的相关配置， 比如设置某个字段为不被索引、记录position等在ES早期版本，一个索引下 是可以有多个Type,从7.0开始，一个索引只有一个Type,也可以说一个Type有一个Mapping定义。 在了解了什么是Mapping之后，接下来对Mapping的设置做下介绍： Mapping设置 在创建一个索引的时候，可以对dynamic进行设置，可以设成false、true或者strict。 比如一个新的文档，这个文档包含一个字段，当Dynamic设置为true时，这个文档可以被索引进ES,这个 字段也可以被索引，也就是这个字段可以被搜索，Mapping也同时被更新；当dynamic被设置为false时候，存在新增字段的数据写入，该数据可以被索引，但是新增字段被丢弃；当设置成strict模式时候，数据写入直接出错。 可以用这个对某个索引进行修改字段的属性，如果type是 keyword 值，那么在查询的时候，就不会分词，需要全匹配，如果是 text，就会分词。 10. Elasticsearch Analyzer详解 Elasticsearch 中文本分析Analysis是把全文本转换成一系列的单词(term/token)的过程，也叫分词。文本分析是使用分析器Analyzer来实现的，Elasticsearch内置了分析器，用户也可以按照自己的需求自定义分析器。 为了提高搜索准确性，除了在数据写入时转换词条，匹配Query语句时候也需要用相同的分析器对查询语句进行 分析。 10.1 Analyzer的组成 Analyzer由三部分组成：Character Filters、Tokenizer、Token Filters 10.1.1 Character Filters Character Filters字符过滤器接收原始文本text的字符流，可以对原始文本增加、删除字段或者对字符做转 换。一个Analyzer分析器可以有0-n个按顺序执行的字符过滤器。 10.1.2 Tokenizer Tokenizer 分词器接收 Character Filters 输出的字符流，将字符流分解成的那个的单词，并且输出单词流。例如 空格分词器会将文本按照空格分解，将&quot;Quick brown fox!&quot;转换成[Quick,brown,fox!]。分词器也负责记录每个 单词的顺序和该单词在原始文本中的起始和结束偏移offsets。 一个 Analyzer分析器 有且只有1个分词器。 10.1.3 Token Filters Token Filter单词过滤器接收分词器Tokenizer输出的单词流，可以对单词流中的单词做添加、移除或者转换操 作，例如lowercase token filters会将单词全部转换成小写，stop token filters会移除the、and这种通用单 词，synonym token filter会往单词流中添加单词的同义词。 Token filters不允许改变单词在原文档的位置以及起始、结束偏移量。 一个Analyzer分析器可以有0-n个按顺序执行的单词过滤器。 10.2 Elasticsearch内置的分词器 例子：The2 QUICK Brown-Foxes jumped over the lazy dog's bone.. 10.2.1 Standard Analyzer ·默认分词器 ·按词分类 ·小写处理 输出： 10.2.2 Simple Analyzer ·按照非字母切分，非字母则会被去除 ·小写处理 输出： 10.2.3 Stop Analyzer 小写处理，停用词过滤(the,a,is) #simple 输出： 10.2.4 Whitespace Analyzer 按照空格切分，不转小写 输出： 10.2.5 Keyword Analyzer 不分词，直接将输入当做输出 输出： 10.2.6 Patter Analyzer 正则表达式，默认IW+ 通过正则表达式进行分词 输出： 10.2.7 Language Analyzer 提供了30多种常见语言的分词器 支持语言：arabic,armenian,basque,bengali,,bulgarian,catalan,,czech,dutch,english,finnish,french,galician, german,hindi,hungarian,indonesian,irish,italian,latvian,lithuanian,norwegian,portuguese,romanian,russian, sorani,spanish,swedish,turkish. 输出： 中文分词要比英文分词难，英文都以空格分隔，中文理解通常需要上下文理解才能有正确的理解，比如苹果，不大好吃 和 苹果，不大，好吃吻，这两句意思就不一样。 10.2.8 ICU Analyzer ElasticSearch默认以每个字对中文分隔，无法满足我们的需求。ICU Analyzer使用国际化组件Unicode(ICU)函数 库提供丰富的处理Unicode,更好支持中文分词，ICU Analyzer不是默认分词器，需要先安装插件，安装命令 输出： 常用的插件分词器 11. 分词的重要性 11.1 文本分词 单词是语言中重要的基本元素。一个单词可以代表一个信息单元，有着指代名称、功能、动作、性质等作 用。在语言的进化史中，不断有新的单词涌现，也有许多单词随着时代的变迁而边缘化直至消失。根据统计， 《汉语词典》中包含的汉语单词数目在37万左右，《牛津英语词典》中的词汇约有17万。 理解单词对于分析语言结构和语义具有重要的作用。因此，在机器阅读理解算法中，模型通常需要首先对语 句和文本进行单词分拆和解析。 分词(tokenization)的任务是将文本以单词为基本单元进行划分。由于许多词语存在词型的重叠，以及组合 词的运用，解决歧义性是分词任务中的一个挑战。不同的分拆方式可能表示完全不同的语义。如在以下例子中， 两种分拆方式代表的语义都有可能： 11.2 分词的意义 （NLP） 11.2.1 将复杂问题转化为数学问题 在机器学习的文章中讲过，机器学习之所以看上去可以解决很多复杂的问题，是因为它把这些问题都转化为了数学问题。而NLP也是相同的思路，文本都是一些「非结构化数据」，我们需要先将这些数据转化为「结构化数据」，结构化数据就可以转化为数学问题了，而分词就是转化的第一步。 11.2.2 词是一个比较合适的粒度 词是表达完整含义的最小单位。 字的粒度太小，无法表达完整含义，比如”鼠“可以是”老鼠“，也可以是”鼠标“。 而句子的粒度太大，承载的信息量多，很难复用。比如”传统方法要分词，一个重要原因是传统方法对远距离依赖的建模能力较弱。&quot; 11.3 中英文分词的3个典型区别 区别1： 分词方式不同，中文更难 英文有天然的空格作为分隔符，但是中文没有。所以如何切分是一个难点，再加上中文里一词多意的情况非 常多，导致很容易出现歧义。下文中难点部分会详细说明。 区别2： 英文单词有多种形态 英文单词存在丰富的变形变换。为了应对这些复杂的变换，英文NLP相比中文存在一些独特的处理步骤，我 们称为词形还原(Lemmatization)和词干提取(Stemming)。中文则不需要 词性还原：does,done,doing,did需要通过词性还原恢复成do。 词干提取：cities,children,teeth这些词，需要转换为city,child,tooth”这些基本形态 区别3： 中文分词需要考虑粒度问题 例如「中国科学技术大学」就有很多种分法： 粒度越大，表达的意思就越准确，但是也会导致召回比较少。所以中文需要不同的场景和要求选择不同的粒度。这个在英文中是没有的。 11.4 中文分词的3大难点 难点1：没有统一的标准 目前中文分词没有统一的标准，也没有公认的规范。不同的公司和组织各有各的方法和规则 难点2：歧义词如何切分 例如「兵乓球拍卖完了」就有2种分词方式表达了2种不同的含义： 难点3：新词的识别 信息爆炸的时代，三天两头就会冒出来一堆新词，如何快速的识别出这些新词是一大难点。比如当年「蓝瘦香菇」大火，就需要快速识别。 11.5 3种典型的分词方法 分词的方法大致分为3类： 给予词典匹配的分词方式 优点：速度快、成本低 缺点：适应性不强，不同领域效果差异大 基本思想是基于词典匹配，将待分词的中文文本根据一定规则切分和调整，然后跟词典中的词语进行匹配，匹配成功则按照词典的词分词，匹配失败通过调整或者重新选择，如此反复循环即可。代表方法有基于正向最大匹配和基于逆向最大匹配及双向匹配法。 基于统计的分词方法 优点：适应性较强 缺点：成本较高，速度较慢 这类目前常用的是算法是HMM、CcRF、SVM、深度学习等算法，比如stanford、Hanlp?分词工具是基于CRF算 法。以CF为例，基本思路是对汉字进行标注训练，不仅考虑了词语出现的频率，还考虑上下文，具备较好的学习 能力，因此其对歧义词和未登绿词的识别都具有良好的效果。 基于深度学习 优点：准确率高、适应性 缺点：成本高，速度慢 例如有人员尝试使用双向LSTM+CF实现分词器，其本质上是序列标注，所以有通用性，命名实体识别等都可以使用该模型，据报道其分词器字符准确率可高达97.5%。 常见的分词器都是使用机器学习算法和词典相结合，一方面能够提高分词准确率，另一方面能够改善领域适应性。 11.6 中文分词工具 下面排名根据GitHub上的star数排名： jieba Hanlp IK Stanford分词 ansj分词器 哈工大LTP KCWS分词器 清华大学THULAC ICTCLAS 11.7 英文分词工具 Keras Spacy Gensim NLTK 11.8 总结 分词就是将句子、段落、文章这种长文本，分解为以字词为单位的数据结构，方便后续的处理分析工作。 分词的原因： 将复杂问题转化为数学问题 词是一个比较合适的粒度 深度学习时代，部分任务中也可以「分字」 中英文分词的3个典型区别： 分词方式不同，中文更难 英文单词有多种形态，需要还原和词干提取 中文分词需要考虑粒度问题 中文分词的3大难点 没有统一的标准 歧义词如何切分 新词的识别 3个典型的分词方式： 基于词典匹配 基于统计 基于深度学习 12. IK分词器安装和配置 下载的版本一定要和ES版本一致。 解压出来到 es的 plugins 文件夹下即可，并改文件夹名为 ik 再执行 自己增加中文词库 自己建立一些文件， 再重启容器 ","link":"https://hzqyihui.github.io/post/elasticsearch-xue-xi/"},{"title":"什么是elasticsearch","content":"1. 什么是elasticsearch? mysql 面临的问题 性能低下 没有相关性排名 -刚需 无法全文搜索 搜索不准确 -没有分词 什么是全文搜索 我想找简历：只要出现java、go原本的需求就是只要简历中出现了 go分布式elasticsearch都是我要的 我们生活中的数据总体分为两种：结构化数据和非结构化数据。 结构化数据： 指具有固定格式或有限长度的数据，如麴居库，元数据等。 非结构化数据： 指不定长或无固定格式的数据，如邮件，word文档等。 非结构化数据又一种叫法叫全文数据。 按照数据的分类，搜索也分为两种： 对结构化数据的搜索：如对数据库的搜索，用SQL语句。再如对元数据的搜索，如利用windows搜索对文 件名，类型，修改时间进行搜索等。 对非结构化数据的搜索：如利用windows的搜索也可以搜索文件内容，LinuxT的grep命令，再如用 Google和百度可以搜索大量内容数据。 对非结构化数据也即对全文数据的搜索主要有两种方法： —种是顺序扫描i4(Serial Scanning):所谓顺序扫描，比如要找内容包含某一T字符串的文件，就是一个文档一个文档的看，对于每一个文档，从头看到尾，如果此文档包含此字符串，则此文档为我们要找的文件，接着看下f文件，直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容，只是相当的慢。假如有一个80G硬盘，如果想在上面找到一个内容包含某字符串的文件，可能需要几个小时的时间。Liux下的grep命令也是这一种方式。这是一种比较原始的防法，但对于小数据量的文件，这种方法还是最直接。最方便的。但是对于大数据量的文件，这种方法的速度就很慢。 另一种是全文检索(full-text Search):即先建立索引，再对索引进行搜索。索引是从非结构化数据中提取出之后重新组织的信息。 什么是elasticsearch Elasticsearch是一个分布式可扩展的实时搜索和分析引擎，一个建立在全文搜索引擎Apache Lucene(TM)基础上的搜索引擎，当然Elasticsearch并不仅仅是Lucene那么简单，它不仅包括了全文搜索功能，还可以进行以下工作： 分布式实时文件存储，并将每一个字段都编入索引，使其可以被搜索。 实时分析的分布式搜索引擎。 可以扩展到上百台服务器，处理PB级别的结构化或非结构化数据。 ES的适用场景 维基百科 The Guardian、新闻 Stack Overflow Github 电商网站、检索商品 日志数据分析、logstash:采集日志、ES进行复杂的数据分析(ELK) 商品价格监控网站、用户设定价格阈值 BI系统、商业智能、ES执行数据分析和挖掘 ES特点 可以作为一个大型的分布式集群（数百台服务器）技术，处理PB级数据，服务大公司，可以运行在单机上，服务小公司。 ES不是什么新技术，主要是将全文检索、数据分析以及分布式技术合并在一起，才形成了独一无二的ES.lucene(全文检索)、商用的数据分析软件、分布式数据库(mycat) 对用户而言，是开箱即用，非常简单，作为中小型的应用，直接3分钟部署S,就可以作为生产环境的系统使用，数据量不大，操作不是很复杂。 数据库的功能面对很多领域是不够用的（事务，还有各种联机事务的操：特殊的功能，比如全文检索、同义词处理、相关度排名、复杂数据分析、海量数据近实时处理：S作为传统数据库的一个补充，提供了数据库所不能提供的很多功能。 ","link":"https://hzqyihui.github.io/post/shi-me-shi-elasticsearch/"},{"title":"Redis 的 RedLock：","content":"Redis 的 RedLock： 面试可能会问，需要重点看看 基于Redis的分布式锁在集群中的问题 多节点redis实现的分布式锁算法（RedLock）：有效防止单点故障 假设有5个完全独立的redis主服务器 获取当前时间戳 client尝试按照顺序使用相同的key,value获取所有的redis服务的锁，在获取锁的过程中的获取时间爱你比锁过期时间短很多，这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。 比如TTL为5s，设置获取锁最多1s，所以如果1秒内无法获取锁，就放弃获取这个锁，从而尝试获取下个锁 client通过获取所有能获取的锁后的时间减去第一步的时间，这个时间差要小于TTL时间并且至少有3个redis实例成功获取锁，才算真正的获取锁成功。 如果成功获取锁，则锁的真正有效时间是TTL减去第三步的时间差的时间；比如：TTL是5s，获取所有锁用了2s，则真正锁有效时间为3s,（其实应该再减去时钟漂移） 如果客户端由于某些原因获取锁失败，便会开始解锁所有的redis实例，因为可能已经获取了小于3个锁，必须释放否则影响其他client获取锁。 算法示意图如下： 什么是时钟漂移？ 如果redis服务器的机器时钟发生了向前跳跃，就到导致这个key过早超时失效，比如客户端1拿到锁后，key的过期时间是12.02分，但redis服务器本身的始终比客户端快了2分钟，导致key 在 12.00 的时候就失效了，这时候，如果客户端1还没有释放锁的话，就可能导致多个客户端同时持有同一把锁的问题。】 RedLock算法是否是异步算法？ 可以看成是同步算法；因为 即使进程间（多个电脑之间）没有同步时钟，但是每个进程时间流速大致相同；并且时钟漂移相对于TTL较小，可以忽略，所以可以看成是 同步算法（不够严谨，算法上要算上时钟漂移，因为如果两个电脑在地球两端，则时钟漂移非常大） RedLock失败重试 当client不能获取锁时，应该在随机事件后重试获取锁，并且最好在同一时刻并发的把set命令发送给所有的redis实例；而且对于已经获取锁的client在完成任务后要及时释放锁，这是为了节省时间 RedLock释放锁 由于释放锁时会判断这个锁的value是不是自己设置的，如果是才删除，所以在释放锁时非常简单，只要向所有的实例发送释放锁的命令，不用考虑能否成功释放。 RedLock注意点（Safety argument） 先假设client获取所有实例，所有实例包含相同的Key和货期时间TTL，但每个实例set命令时间不同，导致不能同时过期，第一个set命令之前是T1, 最后一个set 命令后为T2，则此client有效获取锁的最小时间为TTL-(T2-T1)-时钟漂移； 对于以 N/2 + 1（即一半以上）的方式判断获取锁是否成功，是因为如果小于一半判断为成功的话，有可能会出现多个client都成功获取锁的情况，从而使锁失效。 一个client锁大多数示例耗费的时间大于或接近锁的过期时间，就认为锁无效，并且解锁这个redis实例（不执行业务）吗，只要子啊TTL时间内成功获取一半以上的锁便是有效锁，否则无效。 系统有活性的三个特征 能够自动释放锁 在获取锁失败（不到一半以上），或任务完成后能够自动释放锁，不用等到其自动过期 在client重试获取锁前（第一次失败到第二次重试时间间隔）大于第一次获取锁消耗的时间 重试获取锁要有一定次数限制 RedLock性能及崩溃恢复的相关解决方法 如果redis没有持久化功能，在clientA获取锁成功后，所有Redis重启，clientB能够再次获取到锁，这样违反了锁的排他互斥性 如果启动AOF永久化存储，事情会好些，举例：当重启redis后，由于redis过期机制是按照unix时间戳走的，所以在重启后，会按照规定的时间过期，不影响业务；但是由于AOF同步到磁盘的方式默认是每秒-次，如果在1秒内断点，会导致数据丢失，立即重启会造成锁互斥性失效，但如果同步磁盘方式使用always（每个命令都同步到磁盘）造成性能急剧下降，所以在锁完全有效性和性能方面要有所取舍 有效解决既保证锁完全有效性及性能高效及及时断电情况的方式 是 redis 同步到磁盘方式保持默认的每秒，在 redis无论因为什么原因，停掉后要等待TTL时间后再重启（延迟重启）；缺点是：在TTL时间内服务相当于暂停状态 总结： TTL市场要大于正常业务执行的时间 + 获取所有redis服务消耗时间 + 时钟漂移 获取redis所有服务消耗时间要远小于 TTL 时间，并且获取成功的锁个数要子啊 总数的一半以上 N/2 +1 尝试获取每个redis实例锁时的时间要远小于TTL时间 尝试获取所有锁失败后 重新尝试一定要有 一定次数的 显示 在redis 崩溃后（无论一个还是所有），要延迟TTL时间，重启redis 在实现多redis节点时，要结合单节点分布式锁算法，共同实现 ","link":"https://hzqyihui.github.io/post/redis-de-redlock/"},{"title":"常见的分布式锁的实现方案：","content":"常见的分布式锁的实现方案： 基于MySQL的悲观锁，乐观锁 基于Redis的分布式锁 基于zookeeper的分布式锁 基于MySQL来实现 悲观锁 悲观锁与乐观锁是人们定义出来的概念，你可以理解为一种思想，是处理并发资源的常用手段。 不要把他们与MySQL中提供的锁机制（表锁，行锁，排它锁，共享锁）混为一谈。 一、悲观锁 顾名思义，就是对于数据的处理持悲观态度，总认为会发生并发冲突，获取和修改数据时，别人会修改数据。所以在整个数据处理过程中，需要将数据锁定。 悲观锁的实现，通常依靠数据库提供的锁机制实现，比如MySQL的排它锁，select ... for update来实现悲观锁。 例子：商品秒杀过程中，库存数量的减少，避免出现超卖的情况。 解析： for update 代表获取了一把锁，在没有默认提交的情况下，其他语句再想获取锁，就会等待，或者其他语句想要直接改变数据，比如执行update delete 语句，也会等待。知道获取锁的语句提交后，后面的语句才能再次获取到锁，for update 这把锁是行锁。 乐观锁 机制，可以尝试在数据上加个版本号的字段。 乐观锁还有个 CAS 机制 CAS（Compare-and-Swap），即比较并替换，是一种实现并发算法时常用到的技术，Java并发包中的很多类都使用了CAS技术。 CAS是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令，这个指令会对内存中的共享数据做原子的读写操作。其作用是让CPU比较内存中某个值是否和预期的值相同，如果相同则将这个值更新为新值，不相同则不做更新。 CAS的缺点： CAS虽然很高效的解决了原子操作问题，但是CAS仍然存在三大问题。 循环时间长开销很大。 只能保证一个共享变量的原子操作。 ABA问题。 循环时间长开销很大： 我们可以看到getAndAddInt方法执行时，如果CAS失败，会一直进行尝试。如果CAS长时间一直不成功，可能会给CPU带来很大的开销。在分析Unsafe源代码的时候我们已经提到，在Unsafe的实现中使用了自旋锁的机制。在该环节如果CAS​操作失败，就需要循环进行CAS操作(do while循环同时将期望值更新为最新的)，如果长时间都不成功的话，那么会造成CPU极大的开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升。 只能保证一个共享变量的原子操作： 当对一个共享变量执行操作时，我们可以使用循环CAS的方式来保证原子操作，但是对多个共享变量操作时，循环CAS就无法保证操作的原子性，这个时候就可以用锁来保证原子性。在最初的实例中，可以看出是针对一个共享变量使用了CAS机制，可以保证原子性操作。但如果存在多个共享变量，或一整个代码块的逻辑需要保证线程安全，CAS就无法保证原子性操作了，此时就需要考虑采用加锁方式（悲观锁）保证原子性，或者有一个取巧的办法，把多个共享变量合并成一个共享变量进行CAS操作。 什么是ABA问题？ABA问题怎么解决？ CAS 的使用流程通常如下：1）首先从地址 V 读取值 A；2）根据 A 计算目标值 B；3）通过 CAS 以原子的方式将地址 V 中的值从 A 修改为 B。 但是在第1步中读取的值是A，并且在第3步修改成功了，我们就能说它的值在第1步和第3步之间没有被其他线程改变过了吗？ 如果在这段期间它的值曾经被改成了B，后来又被改回为A，那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题，提供了一个带有标记的原子引用类“AtomicStampedReference”，它可以通过控制变量值的版本来保证CAS的正确性。因此，在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性，如果需要解决ABA问题，改用传统的互斥同步可能会比原子类更高效。 https://www.cnblogs.com/kismetv/p/10787228.html 基于Redis实现分布式锁 redsync源码解析： setnx的作用： 将获取和设置值变成原子性操作 如果服务挂掉了，死锁 设置过期时间 如果设置了过期时间，但时间到了，业务逻辑仍然没有执行完？怎么办 在过期之前，继续延长。 需要自己去启动协程完成延时的操作 延时的接口可能会带来负面影响，如果其中一个服务Hang住了，原本2s就能执行完，但是你hang住了，那么你就会一直去申请延长锁，导致别人永远获取不到锁，这个很要命。 针对上面那一点，增加一个最大尝试延长次数。也就解决了，最后一定会释放锁的。 分布式锁需要解决的问题，基于Lua脚本去做 互斥性 - stenx 死锁 安全性 锁只能被持有该锁的用户删除，不能被其他用户删除。 当时设置的value值是多少，只有当时的 goroutine才能知道。 在删除的时候取出redis中的值和当前自己保存下来的值对比一下。 即使这样实现了分布式，还是有问题， redlock 来解决。 redis lua 脚本 核心原理： redis 提供的 setnx 命令，就是核心， setnx 是原子性的命令。 可以引用 redsync 的一个库，能实现分布式锁 ","link":"https://hzqyihui.github.io/post/chang-jian-de-fen-bu-shi-suo-de-shi-xian-fang-an/"},{"title":"单机锁","content":"锁 单机锁 ","link":"https://hzqyihui.github.io/post/dan-ji-suo/"},{"title":"Go工具-随机获取可用端口并监听：","content":" ","link":"https://hzqyihui.github.io/post/go-sui-ji-huo-qu-ke-yong-duan-kou-bing-jian-ting/"},{"title":"负载均衡算法：","content":"负载均衡算法： 在分布式系统重，多台服务器同时提供一个服务，并统一到服务配置中心进行管理，消费者通过查询服务配置中心，获取到服务地址列表，需要选取其中一台来发起RPC远程调用。如何选择，则取决于具体的负载均衡算法，对应于不同的场景，选择的负载均衡算法也不尽相同。负载均衡算法种类有很多种，常见的负载均衡算法包括轮训法，随机法，源地址哈希法，加权轮询法，加权随机法，最小连接法等，应根据具体的使用场景选取对应的算法。 轮询法（Round Robin）法 轮询法很容易实现，将请求按顺序轮流分配到后台服务器上，均衡的对待每一台服务器，而不关心服务器实际的连接数和当前的系统负载。 随机法 通过系统随机函数，根据后台服务器列表的大小值来随机选取其中一台进行访问。由概率统计理论可以得知，随着调用量的增大，其实际效果越来越接近于平均分配流量到后台的每一台服务器，也就是轮训法的效果。 源地址哈希法（IP hash）（可以继续看看 一致性hash算法） 源地址哈希法的思想史格努服务消费者请求的IP地址，通过哈希函数计算得到一个哈希值，将此哈希值和服务器列表的大小进行取模运算，得到的结果便是要访问的服务器地址的序号。采用源地址哈希法进行负载均衡，相同的IP客户端，如果服务器列表不变，将映射到同一个后台服务器进行访问。 加权轮询法（Weight Round Robin） 不同的后台服务器可能机器的配置和当前系统的负载并不相同，因此它们的抗压能力也不一样。跟配置高，负载低的机器分配更高的权重，使其能处理更多的请求，而配置低，负载高的机器，则给其分配较低的权重，降低其系统负载，加权轮询很好的处理了这一问题，并将请求按照顺序根据权重分配给后端 加权随机法（Weight Random） 加权随机法跟加权轮训法类似，根据后台服务器不同的配置和负载情况不同，配置不同的权重。不同的是，它是按照权重来随机选取服务器的，而非顺序的。 最小连接数法 前面我们费劲心思来实现服务消费者请求次数分配的均衡，我们知道这样做事没错的，可以为后端的多台服务器平均分配工作量，最大程度的提高服务器的利用率，但是，实际上，请求次数的均衡并不代表负载的均衡。因此我们需要介绍最小连接数法，最小连接数法比较灵活和智能，由于后台服务器的配置不尽相同，对请求的处理有快有慢，它正式根据后端服务器当前的连接情况，动态的选取其中当前积压连接数最少的一台服务器来处理当前请求，尽可能的固体膏后台服务器利用率，将负载合理的分流到每一台服务器。 可参考地址：https://blog.csdn.net/qq_41455420/article/details/79846199 ","link":"https://hzqyihui.github.io/post/fu-zai-jun-heng-suan-fa/"},{"title":"负载均衡策略：","content":" 负载均衡策略： 1. 集中式 Load Balance 集中式LB方案，如下图。首先，服务的消费方和提供方不直接耦合，而是在服务消费者和服务提供者之间有一个独立的LB（LB通常是专门的硬件设备如F5，或者基于软件如LVS，HAProxy等） https://www.processon.com/view/link/63145d3263768972fedc2624 LB上有所有服务的地址映射表，通常由运维配置注册，当服务消费方调用某个目标服务时，他向LB发起请求，由LB以某种策略（比如Round-Robin）做负载均衡后将请求转发到目标服务。 LB一般具有健康检查能力，能自动摘除不健康的服务实例。 服务消费方如何发现LB呢？通常的做法是通过DNS，运维人员为服务配置一个DNS域名，这个域名指向LB。这种方案基本可以否决，因为它有致命的缺点：所有服务调用流量都经过load balance的服务器，所有load balance服务器成了系统的单点，一般LB发生故障对整个系统的影响是灾难的，为了解决这个问题，必然需要对这个Load banlance部件做分布式处理（部署多个实例，冗余，然后解决一致性问题等全家桶解决方案），但这样做会徒增非常多的复杂度。 2. 进程内Load Balance 进程内Load Balance。将Load Balance的功能和算法以SDK的方式实现在客户端进程内。先看架构图。 可看到引入了第三方：服务注册中心。它做两件事： 1. 维护服务提供方的节点列表，并检测这些节点的健康程度。检测的方式是：每个节点都部署成功，都通知服务注册中心；然后一直和注册中心保持心跳。 2. 允许服务调用方注册感兴趣的事件，把服务提供方的变化情况推送到服务调用方。 这种方案下，整个Load Balance的过程是这样的： 1. 服务注册中心维护所有节点的情况 2. 任何一个节点想要订阅其他服务提供方的节点列表，想服务注册中心注册 3. 服务注册中心将服务提供方的列表（以长连接的方式）推送到消费方 4. 消费方接收到消息后，在本地维护一份这个列表，并自己做Load Balance。 可见，服务注册中心充当什么角色？他是唯一一个知道整个集群内部所有节点情况的中心。所以对它的可用性要求非常高，这个组件可以用Zookeeper实现。 这种方案的缺点是：每个语言都要研究一套SDK，如果公司内的服务使用的语言五花八门的话，这方案的成本会很高。第二点是：后续如果要对客户库进行升级，势必要求服务调用方修改代码并重新发布，所以该方案的升级推广有不小的阻力。 3. 独立进程Load Balance 该方案是针对第二种方案的不足而提出的一种折中方案，原理和第二种方案基本类似，不同之处是，他将LB和服务发现功能从进程内移出来，变成主机上的一个独立进程，主机上的一个或者多个服务要方案目标服务时，他们都通过同一主机上的独立LB进程做服务发现和负载均衡。如图： 这个方案解决了上一种方案的问题，不需要为不同语言开发客户库，LB的升级不需要服务调用方改代码。但新引入的问题是：这个组件本身的可用性谁来维护？还要再写一个watchdog去监控这个组件？另外，多了一个环节，就多了一个出错的可能，线上出问题了，也多了一个需要排查的环节。 ","link":"https://hzqyihui.github.io/post/fu-zai-jun-heng-ce-lue/"},{"title":"JWT集成在Gin框架中","content":"JwtConfig.go文件 中间件jwt.go 文件 在外层使用： ","link":"https://hzqyihui.github.io/post/jwt-ji-cheng-zai-gin-kuang-jia-zhong/"},{"title":"Go工具-Viper配置文件管理器","content":" 将配置文件映射成struct viper还能监控配置文件变化 ","link":"https://hzqyihui.github.io/post/go-gong-ju-viper-pei-zhi-wen-jian-guan-li-qi/"},{"title":"二叉树层序遍历","content":"PHP代码 ","link":"https://hzqyihui.github.io/post/er-cha-shu-ceng-xu-bian-li/"},{"title":"二叉树后序遍历","content":"PHP代码非递归版 ","link":"https://hzqyihui.github.io/post/er-cha-shu-hou-xu-bian-li/"},{"title":"二叉树中序遍历","content":"PHP代码非递归版 ","link":"https://hzqyihui.github.io/post/er-cha-shu-zhong-xu-bian-li/"},{"title":"二叉树先序遍历","content":"PHP代码非递归版： ","link":"https://hzqyihui.github.io/post/er-cha-shu-xian-xu-bian-li/"},{"title":"Dockerfile 的语法使用：","content":"制作镜像： 制作镜像： Docker多阶段编译 如果要从一个镜像直接复制到另一个镜像： 使用alpine镜像，能够极大的缩小镜像，容器的大小。 ","link":"https://hzqyihui.github.io/post/dockerfile-de-yu-fa-shi-yong/"},{"title":"Nginx重写和转发规则：","content":"Nginx转发规则： 在Nginx的官方文档中定义的location的语法结构为： 其中，uri变量是待匹配的请求字符串，可以是不含正则表达的字符串，如/myserver.php等；也可以是包含有正则表达的字符串，如 .php$（表示以.php结尾的URL）等。为了下文叙述方便，我们约定，不含正则表达的uri称为“标准uri”，使用正则表达式的uri称为“正则uri”。 其中方括号里的部分，是可选项，用来改变请求字符串与 uri 的匹配方式。在介绍四种标识的含义之前，我们需要先了解不添加此选项时，Nginx服务器是如何在server块中搜索并使用location块的uri和请求字符串匹配的。 在不添加此选项时，Nginx服务器首先在server块的多个location块中搜索是否有标准uri和请求字符串匹配，如果有多个可以匹配，就记录匹配度最高的一个。然后，服务器再用location块中的正则uri和请求字符串匹配，当第一个正则uri匹配成功，结束搜索，并使用这个location块处理此请求；如果正则匹配全部失败，就使用刚才记录的匹配度最高的location块处理此请求。 alias与root的区别 root 实际访问文件路径会拼接URL中的路径，alias 实际访问文件路径不会拼接URL中的路径 示例如下： 显然，第二次重定向是不需要的，本意是访问/flash/的时候，直接访问对应目录下的html静态文件。 但因为root拼接flash导致找不到对应文件，要重写url，去掉flash这个模块前缀，使用了rewrite, 而rewrite会返回302重定向。 接下来，我们修改root为alias 全局变量 主机名（server_name）匹配 从上到下的优先级为从高到低 明确的server_name名称，如www.qq.com 前缀通配符，如*.qq.com或. qq.com 后缀通配符，如www.qq.* 正则表达式，如~[a-z]+.qq.com Location查找规则 从上到下的优先级为从高到低 等号类型，精确匹配，如location = / {} ^~类型，前缀匹配，不支持正则，如location ^~ /user {} 和*类型，正则匹配，区分大小写，*不区分大小写，如location ~ ^/user {} 常规字符串匹配类型，如location / {}或location /user {} Try_files规则 try_files $uri uri//index.php假设请求为http://www.qq.com/test，则uri/ /index.php 假设请求为http://www.qq.com/test，则uri//index.php假设请求为http://www.qq.com/test，则uri为test 查找/$root/test文件 查找/$root/test/目录 发起/index.php的内部“子请求” try_files常用到的变量 所以你会经常看见有这样的写法： 而有些人会写成这样： 其实第二种写法肯定是不好的，因为万一args为空，那么就留下了一个无用的问号，而用args为空，那么就留下了一个无用的问号，而用args为空，那么就留下了一个无用的问号，而用is_args则能避免这个问题。 querystring与query_string与querys​tring与args相同。 $document_root root指令指定的值。 $request_filename 当前连接请求的文件路径，由root或alias指令与URI请求生成（不包住参数）。 $request_uri 原始请求uri（即url中除去协议和域名的部分，比如：http://127.0.0.1/index.php的uri即为/index.php Rewrite规则 上面的rewrite规则会将文件名改写到参数中 负载均衡 例子如下 ","link":"https://hzqyihui.github.io/post/nginx-chong-xie-he-zhuan-fa-gui-ze/"},{"title":"HTTP版本：","content":"版本： HTTP 1.0 : 已废弃 HTTP 1.1 ： 2.1 HTTP 1.1 使用了摘要算法进行身份验证 2.2 默认使用了长连接，长连接就是只需要一次建立就可以传输多次数据，传输完成后，只需要一次切断连接即可。长连接的连接时长可以通过请求头中的 keep-alive 来设置 2.3 新增了 e-tag If-Unmodified-Since If-Match If-None-Match 等缓存控制标头来控制缓存失效。 2.4 支持断点续传，通过使用请求头中的range 来实现 2.5 使用了虚拟网络，在一台物理服务器上可以存在多个虚拟主机，并且他们共享一个IP地址。 HTTP 2.0 3.1 2015年开发出来的标准，主要做出以下改变。 3.2 头部压缩：由于HTTP 1.1 经常会出现User-Agent, Cookie， Accept，Server，Range等字段可能会占用几百甚至几千字段，而Body却经常只有几十字节，所以导致头部偏重。HTTP 2.0 使用HPACK算法进行压缩。 3.3 二进制格式：HTTP2.0 使用了更加靠近TCP/IP的二进制格式，而抛弃了ASCII码，提升了解析效率。 3.4 强化安全，由于安全已成为重中之重，所以HTTP 2.0 一般都跑在HTTPS 上的。 3.5 多路复用：即每个请求都是用作连接共享。一个请求对应一个id，这样一个连接上可以有多个请求。 HTTPS与HTTP的一些区别 HTTPS协议需要到CA申请证书，一般免费证书很少，需要交费。 HTTP协议运行在TCP之上，所有传输的内容都是明文，HTTPS运行在SSL/TLS之上，SSL/TLS运行在TCP之上，所有传输的内容都经过加密的。 HTTP和HTTPS使用的是完全不同的连接方式，用的端口也不一样，前者是80，后者是443。 HTTPS可以有效的防止运营商劫持，解决了防劫持的一个大问题。 https://zhuanlan.zhihu.com/p/135947893 ","link":"https://hzqyihui.github.io/post/http-ban-ben/"},{"title":"HTTP请求响应过程：","content":"输入网址后，到拿到结果，发生了什么？ DNS服务器会首先进行域名映射，（第一步是现在电脑本地hosts文件查找，是否能找到，若不能找到，则请求DNS），找到IP地址后，HTTP客户端进程在80端口发起一个到IP对应服务器的TCP链接。 HTTP客户端通过他的套接字向服务器发送一个HTTP请求报文。该报文中包含了 资源路径。 HTTP服务器通过他的套接字，接受了该报文，进行请求的解析工作，并从其存储器（RAM或磁盘）中检索出对象，然后把检索出来的对象进行封装，封装到HTTP响应报文中，闭关通过套接字向客户进行发送。 HTTP服务器随机通知TCP断开TCP连接，实际上是需要等到客户接受完响应报文后才会断开TCP连接。 HTTP客户端接受完响应报文后，TCP连接会关闭。HTTP客户端从响应中提取出报文是一个HTML响应文件，并检查该HTML文件，然后循环检查报文中其他对象 检查完成后，HTTP客户端会把对应的资源通过显示器呈现给用户。 HTTP协议主要由三大部分组成： 起始行：描述请求或响应的基本信息 头部字段：使用key-value 形式更详细的说明报文 消息正文：实际传输的数据，他不一定是纯文本，可以是图片，视频等二进制数据。 每个报文的起始行都是有三个字段组成：请求方法（GET POST），URL字段， HTTP版本字段。 HTTP1.1 ** 第一个问题：现代浏览器在与服务器建立了一个 TCP 连接后是否会在一个 HTTP 请求完成后断开？什么情况下会断开？ ** 默认情况下建立 TCP 连接不会断开，只有在请求报头中声明 Connection: close 才会在请求完成后关闭连接。（详细文档见下面的链接） 第二个问题：一个 TCP 连接可以对应几个 HTTP 请求？ 了解了第一个问题之后，其实这个问题已经有了答案，如果维持连接，一个 TCP 连接是可以发送多个 HTTP 请求的。 第三个问题：一个 TCP 连接中 HTTP 请求发送可以一起发送么（比如一起发三个请求，再三个响应一起接收）？ 在 HTTP/1.1 存在 Pipelining 技术可以完成这个多个请求同时发送，但是由于浏览器默认关闭，所以可以认为这是不可行的。在 HTTP2 中由于 Multiplexing 特点的存在，多个 HTTP 请求可以在同一个 TCP 连接中并行进行。 第四个问题：为什么有的时候刷新页面不需要重新建立 SSL 连接？ 在第一个问题的讨论中已经有答案了，TCP 连接有的时候会被浏览器和服务端维持一段时间。TCP 不需要重新建立，SSL 自然也会用之前的。 第五个问题：浏览器对同一 Host 建立 TCP 连接到数量有没有限制？ 假设我们还处在 HTTP/1.1 时代，那个时候没有多路传输，当浏览器拿到一个有几十张图片的网页该怎么办呢？肯定不能只开一个 TCP 连接顺序下载，那样用户肯定等的很难受，但是如果每个图片都开一个 TCP 连接发 HTTP 请求，那电脑或者服务器都可能受不了，要是有 1000 张图片的话总不能开 1000 个TCP 连接吧，你的电脑同意 NAT 也不一定会同意。 所以答案是：有。Chrome 最多允许对同一个 Host 建立六个 TCP 连接。不同的浏览器有一些区别。 ","link":"https://hzqyihui.github.io/post/http-qing-qiu-xiang-ying-guo-cheng/"},{"title":"TCP和UDP的区别：","content":"TCP和UDP都未与计算机网络模型中的运输层，他们负责传输应用层产生的数据。 UDP 是什么 UDP全称是 User Datagram Protocol， 用户数据报协议。它不需要所谓的握手操作，从而加快了通信速度，允许网络上的其他主机在接收方同意通信之前进行数据传输。 UDP的特点主要有 UDP能够支持容忍数据报丢失的带宽密集型应用程序 UDP具有低延迟的特点 UDP能够发送大量的数据包 UDP能够允许DNS查找，DNS是建立在UDP之上的应用层协议 TCP 是什么 TCP全程是Transmission Control Protocol，传输控制协议。它能够帮助你确定计算机连接到Internet以及它们之间的数据传输。通过三次握手来建立TCP连接，三次握手就是用来启动和确认TCP连接的过程。一旦建立连接后，就可以发送数据了，当数据传输完成后，会通过关闭虚拟电路来断开连接。 TCP的主要特定有 TCO能够确保连接的建立和数据包的发送 TCP支持错误重传机制 TCP支持拥塞控制，能够在网络拥堵的情况下延迟发送 TCP能够提供错误校验和，甄别有害的数据包 TCP 和 UDP TCP 是面向连接的协议 。 UDP 是无连接的协议 TCP 在发送数据前先需要建立连接，然后再发送数据 。 UDP 无需建立连接就可以直接发送大量数据 TCP 会按照特定顺序重新排列数据包 。 UDP 数据包没有固定顺序，所有数据包都相互独立 TCP 传输的速度比较慢 。 UDP 的传输会更快 TCP 的头部字节有 20 字节 。 UDP 的头部字节只需要 8 个字节 TCP 是重量级的，在发送任何用户数据之前，TCP需要三次握手建立连接。 UDP 是轻量级的。没有跟踪连接，消息排序等。 TCP 会进行错误校验，并能够进行错误恢复 。 UDP 也会错误检查，但会丢弃错误的数据包。 TCP 有发送确认。 UDP 没有发送确认 TCP 会使用握手协议，例如 SYN，SYN-ACK，ACK。 UDP无握手协议 TCP 是可靠的，因为它可以确保将数据传送到路由器。 UDP 中不能保证将数据传送到目标。 https://zhuanlan.zhihu.com/p/135947893 ","link":"https://hzqyihui.github.io/post/tcp-he-udp-de-qu-bie/"},{"title":"插入排序","content":"PHP版本： Golang版本： ","link":"https://hzqyihui.github.io/post/cha-ru-pai-xu/"},{"title":"Go工具-proto文件编译","content":" proto文件编译： 也可以用最简单的语句 生成grpc 的代码:plugins=grpc, 新写法： 生成grpc-gateway_out 遇到的问题，在goland中， 如果 打开的文件夹 不是 GO_PATH 目录的话， 就会存在，生成的 pb.proto 找不到包的情况。 所以 需要 打开 bin,pkg, src 同级（存在疑惑，此句不一定对） 语法： option go_package = &quot;gen/;trip&quot; 代表要在 gen目录下 生成 安装 proto-gen-go 插件： 需要先安装 protoc-gen-grpc-gateway 插件： rpc gateway 的生成, 作用是 把 rpc 的格式， 转换为 restful json api 的方式。以下命令行： trip.ymal文件代码： 参考地址：https://segmentfault.com/a/1190000013339403 特别注意： 如果proto文件结构中的字段是在程序中，是该字段类型的 零值， 那么会产生该字段不会被 marshal出来。实际上是因为标签中 omitempty 的作用 proto练习： 一个 proto 引入另一个 proto ","link":"https://hzqyihui.github.io/post/go-gong-ju-proto-wen-jian-bian-yi/"},{"title":"二分查找算法","content":"PHP版本： Golang版本： ","link":"https://hzqyihui.github.io/post/er-fen-cha-zhao-suan-fa/"},{"title":"sed命令：","content":"sed命令： inux sed 命令是利用脚本来处理文本文件。 sed 可依照脚本的指令来处理、编辑文本文件。 Sed 主要用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。 语法 参数说明： -e &lt;script&gt;或 --expression= &lt;script&gt; 以选项中指定的script来处理输入的文本文件。 -f&lt;script文件&gt;或--file=&lt;script文件&gt; 以选项中指定的script文件来处理输入的文本文件。 -h或--help 显示帮助。 -n或--quiet或--silent 仅显示script处理后的结果。 -V或--version 显示版本信息。 动作说明： a ：新增， a 的后面可以接字串，而这些字串会在新的一行出现(目前的下一行)～ c ：取代， c 的后面可以接字串，这些字串可以取代 n1,n2 之间的行！ d ：删除，因为是删除啊，所以 d 后面通常不接任何东东； i ：插入， i 的后面可以接字串，而这些字串会在新的一行出现(目前的上一行)； p ：打印，亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行～ s ：取代，可以直接进行取代的工作哩！通常这个 s 的动作可以搭配正则表达式！例如 1,20s/old/new/g 就是啦！ 比如 sed 的查找与替换的与 vi 命令类似，语法格式如下： g 标识符表示全局查找替换，使 sed 对文件中所有符合的字符串都被替换，修改后内容会到标准输出，不会修改原文件： 选项 i 使 sed 修改文件: 批量操作当前目录下以 test 开头的文件： ","link":"https://hzqyihui.github.io/post/sed-ming-ling/"},{"title":"xargs命令：","content":"xargs： -i 或者是-I，这得看linux支持了，将xargs的每项名称，一般是一行一行赋值给 {}，可以用 {} 代替。 ","link":"https://hzqyihui.github.io/post/xargs-ming-ling/"},{"title":"firewall命令（防火墙）：","content":"1、firewalld的基本使用 启动： systemctl start firewalld 查看状态： systemctl status firewalld 停止： systemctl disable firewalld 禁用： systemctl stop firewalld 2.systemctl是CentOS7的服务管理工具中主要的工具，它融合之前service和chkconfig的功能于一体。 启动一个服务：systemctl start firewalld.service 关闭一个服务：systemctlstop firewalld.service 重启一个服务：systemctlrestart firewalld.service 显示一个服务的状态：systemctlstatus firewalld.service 在开机时启用一个服务：systemctlenable firewalld.service 在开机时禁用一个服务：systemctldisable firewalld.service 查看服务是否开机启动：systemctlis-enabled firewalld.service 查看已启动的服务列表：systemctllist-unit-files|grep enabled 查看启动失败的服务列表：systemctl--failed 3.配置firewalld-cmd 查看版本： firewall-cmd --version 查看帮助： firewall-cmd --help 显示状态： firewall-cmd --state 查看所有打开的端口： firewall-cmd--zone=public --list-ports 更新防火墙规则： firewall-cmd --reload 查看区域信息: firewall-cmd--get-active-zones 查看指定接口所属区域： firewall-cmd--get-zone-of-interface=eth0 拒绝所有包：firewall-cmd --panic-on 取消拒绝状态： firewall-cmd --panic-off 查看是否拒绝： firewall-cmd --query-panic 那怎么开启一个端口呢 添加 重新载入 查看 删除 查看firewall是否运行,下面两个命令都可以 查看当前开了哪些端口 其实一个服务对应一个端口，每个服务对应/usr/lib/firewalld/services下面一个xml文件。 查看还有哪些服务可以打开 查看所有打开的端口： 更新防火墙规则： ","link":"https://hzqyihui.github.io/post/firewall-ming-ling-fang-huo-qiang/"},{"title":"nohup 命令：","content":"nohup 命令，no hang up（不挂起），用于在系统后台不挂断地运行命令，退出终端不会影响程序的运行。 最后的 &amp; 是指 后台运行， 2&gt;&amp;1 解释： 将标准错误 2 重定向到标准输出 &amp;1 ，标准输出 &amp;1 再被重定向输入到 runoob.log 文件中。 0 – stdin (standard input，标准输入) 1 – stdout (standard output，标准输出) 2 – stderr (standard error，标准错误输出) ","link":"https://hzqyihui.github.io/post/nohup-ming-ling/"},{"title":"安装 FFmpeg：","content":"首次安装 FFmpeg 后，虽然能对音视频进行一些处理，但是 该软件默认 不包含 h264 的编码器，导致若 想编码为 h264的 视频，就会报错。Unknown encoder ‘h264’ ，h264 视频编码的 格式 是能直接在 浏览器 video 标签中直接播放的视频。 源码安装流程： fmpeg安装第三方编码器（encoder）库，ffmpeg编码h264（完） ffmpeg安装第三方编码器（encoder）库 关键词：ffmpeg、编码h264、第三方encoder 安装好了ffmpeg后，如果你使用ffmpeg工具去把某个视频文件转成h264视频编码、mp3音频编码or其他ffmpeg自身不带的xxx编码类型，就会看到报错信息，unknown encoder 'xxx'。此刻你需要的只要去安装其他的编码器就行了，本质上其实是把其他的编码器以库的形式安装好，例如，把正确的libx264.so or libx264.a存放在/usr/lib下 or /usr/local/lib下。 举两个例子吧，视频方面的编码器就拿h264来说，音频方面的例子就拿mp3（mp3lame）来说。 扫盲，Linux下安装一个正规的软件，一般都是三部曲，①、【./configure】（加一些可能的参数，比如enable一些功能，disable一些功能，究竟有哪些功能可以开启和关闭呢？一般通过./configure --help命令来查询），②、【make】（编译），③、【sudo make install】（把生成的二进制应用程序文件和.so和.a复制到/usr/local/下） 一、h264 动手搜一下ffmpeg的工程代码库，会发现每个codec都有一下几个成员变量，但是有好几个codec缺少encoder，h264就是其中一个了。先不管什么原因，ffmpeg没有原生的支持h264，但是你可以查看一下avcodec_register_all这个API，会发现一大片的REGISTER_ENCODER(XXX, xxx) REGISTER_DECODER(XXX, xxx) 这里分很多块，例如/* video codecs /，/ audio codecs /，/ external libraries / 但是你在/ video codecs /这一块却看不到h264的REGISTER_ENDECODER (H264, h264);这句话，如果你坚持往下翻，你会在/ external libraries */这块里面发现REGISTER_ENCODER (LIBX264, libx264);所以ffmpeg是有给h264准备好了接口的，但是需要第三方库来支持。 回到重点，怎么装呢？ 1.先下载x264的工程代码，【git clone git://git.videolan.org/x264.git】。 2.进入x264目录，然后./configure --help看看它的帮助信息，我们这里需要的是x264以.so or .a的形式来支援ffmpeg，所以一般就关注shared和static关键词就可以了。执行./configure --enable-shared --enable-static就行了。 3.完了make &amp;&amp; sudo make install就可以了。 你会发现我们在./configure的时候没有指定前缀--prefix=/usr，很明显，libx264.so和libx264.a就会复制到/usr/local/lib下去，记住这里，等下会因为这里要做一些修改。 二、mp3lame 上面说了h264，相信mp3lame理解起来就简单多了。 1.先下载mp3lame的工程代码，http://sourceforge.net/projects/lame/files/lame/，为什么这里要显得多余的讲一下mp3lame呢，是这样的。大家可以看到x264用的是git，mp3lame是用的sourceforge，不妨再多说一个faac（也是一种原生ffmpeg不支持的音频codec），faac用的是http://sourceforge.net/projects/faac/files/faac-src/，所以每一种codec或者很重要的软件都有一个团队或者社区在维护，所以需要什么东西，尽量去sourceforge或者git上找，其他地方找的可能不够新，可能不完整不正确。 2.然后也是./configure --help先，看看哪些功能是我们需要打开关闭的 3.完了make &amp;&amp; sudo make install就可以了。 很明显，我们又没有指定--prefix-/usr，所以mp3lame的libmp3lame.so和libmp3lame.a就被赋值到了/usr/local/lib下了。 三、重新编译ffmpeg 1.进入ffmpeg目录，./configure --enable-gpl --enable-libx264 --enable-libmp3lame，然后就生成了新的makefile了。 2.执行sudo make clean &amp;&amp; make sudo make install。 3.这样ffmpeg就被重新编译了，完了就可以验证一下，使用ffmpeg工具，把某个视频文件中的视频流转码成h264格式，音频流转码成mp3lame格式，不妨试试。 4.如果你真的尝试了，你应该会看到类似于“libxxx.so找不到”的错误提示，解决办法如下： （1）.表象：ffmpeg运行的时候试图去链接libxxx.so，但是却找不到相应的libxxx.so。 （2）.疑惑：我之前明明安装了libxxx.so的。 （3）.原因：程序运行的时候默认是去/usr/lib下找libxxx.so，但是我们之前安装的确实在/usr/local/lib下，所以造成这个报错。 （4）.解决办法：有很多，我说一种我亲测过的。 在/etc/ld.so.conf文件中添加一行/etc/ld.so.conf，当然是用root用户啦。然后执行ldconfig命令使得刚才的修改生效，完了再运行ffmpeg的转码命令试试，可以了吧。 之前我尝试过用ffplay来播放一个h264编码的视频，结果当然是可以播放，我就天真的以为ffmpeg支持h264了，其实不然，ffmpeg主要还是用来解码，所以部分格式的encoder却没有，所以才会有第三方库支援，为什么原生ffmpeg不支持某些格式呢？原因我暂时不知道。为了解决ffmpeg可以编码h264这个问题，我在网上搜索一些资料，然后总结成上文，在此过程中我还收获了一些Linux的知识，也已经写出。 补充三点： aac：./configure --enable-shared --enable-static 264：./configure --enable-shared --enable-static --disable-asm ffmpeg：./configure --enable-gpl --enable-libx264 --enable-libfaac --enable-nonfree --disable-yasm ","link":"https://hzqyihui.github.io/post/an-zhuang-ffmpeg/"},{"title":"umask命令：","content":"对于root用户，他的umask值是022。当root用户创建目录时，默认的权限就是用最大权限777去掉相应位置的umask值权限，即对于所有者不必去掉任何权限，对于所属组要去掉w权限，对于其他用户也要去掉w权限，所以目录的默认权限就是755；当root用户创建文件时，默认的权限则是用最大权限666去掉相应位置的umask值，即文件的默认权限是644。 umask命令只能临时修改umask值，系统重启之后umask将还原成默认值。如果要永久修改umask值，需要修改/etc/profile文件或是修改/etc/bashrc文件，例如要将默认umask值设置为027，那么可以在文件中增加一行“umask 027”。 /etc/profile和/etc/bashrc都可以用于设置用户登录系统时自动执行某些操作，他们的区别是/etc/profile只在用户第一次登录时被执行，而/etc/bashrc则在用户每次登录加载Bash Shell时都会被执行。 因而，如果是修改/etc/profile文件，将只对新创建的用户生效；而如果是修改/etc/bashrc文件，则对所有用户都生效。 ","link":"https://hzqyihui.github.io/post/umask-ming-ling/"},{"title":"shell 笔记：","content":" shell脚本中 某些符号代表什么意思： 举例说明： 以上脚本，当执行了 ps 后， 本意是 要去匹配是否存在 java -jar dhcamera 的进程， 若发现运行着， 则 ps 这个操作是正确的， 那么 $? 就是这个 退出的状态，当为 0时，正确。 不为0，则错误，即未匹配到任何值，则执行 if 语句块中的 语句 ","link":"https://hzqyihui.github.io/post/shell-bi-ji/"},{"title":"GIT分支与工作流","content":"分支的概念： 往简单说： 就是开发过程中，不同的程序员走不通的路，做不同的事情，这是分支的概念。（岔路口） 官方一点：Git 的分支，其实本质上仅仅是指向提交对象的可变指针，这个可变指针，指向路的终点。如我上次讲的，建立一个Git分支，实际上就是在 .git\\refs\\heads 文件夹下继续建立文件夹或者文件，文件内部是基于的某个分支的最新Head提交对象。 分支的作用： 分支的作用主要在于，对同一个项目，需要同时开发不同的需求，这个时候就需要不同的分支来管理了。 忌讳： Git分支不是代码仓库，其本质只是一个 commit 对象（上节 git核心原理 有提到），Git分支不同于SVN等集中式版本管理工具，SVN的分支有一个拷贝的概念在里面，每一个分支都是一份完整的备份。有些地方就会利用 SVN 的分支来管理不同的仓库。但 分支仍然不应该这么使用。 原因：分支始终有一个合并的概念，若不同分支存储不同仓库，这个合并的操作就无法完成（俩仓库八竿子打不着的关系）。 操作： 通常基于 master/develop 切不同的分支出来，最后开发完成后，合并到某一个分支。该分支即完成使命。 当前现象： 因为现在我们的需求都是由PM一次性给到，一般来说做的需求都比较大，代码新增，更新量都比较大，这就导致多个人在一起并在一个分支开发。 哪种情况出现并行开发：PM出需求频繁，但都是小需求，对哪里进行修改，对哪里进行新增，需要快速上线的话，就可以切出一个分支进行开发，配合使用Jenkins（+Ansible），以达到持续集成+持续部署的目的。 工作流： ​ 在介绍工作流之前首先认识下GIT的几个功能，Issue, Pull Request, Fork。这几个功能在团队协作规范的工作流中都起了相应的作用。下面介绍下各自所处的位置与作用。 Issue： Issue是一个记录的平台。对拥有者来说，可以写下一步的工作计划，或者写某个线上的BUG，需要修复并且记录在这里。而对其他可读的用户来说，就是一个提出问题的平台，用户提出问题，拥有者查看后，进行问题的排查。所以根据我们现在对GIT 提交记录的要求，当使用 fix 或 close时，就可以标记为 close #n或 fix #n，这里的n代表的是问题的id,也就是下面链接中的2 Pull Request Pull Request 在Coder中的流行叫法是PR，中文名叫合并请求，它的作用是 基于某个分支，比对另一个分支的代码，以此来查看哪些人提交了代码，代码变更的详细和多少。如下图所示： 以上可以看到这些是一些普通的变更代码。这样的话，我们就可以实现组内的Code Review，也就是同事之间相互审核代码。PR的作用不仅于此，当一个人没有权限针对某个分支Push代码的时候，就可以提交PR，由有Write权限的人进行合并，合并后代码即进入到目标分支。常见的使用场景：开发完功能分支后，创建一个PR到master，等QA验证完成后，即可合并。 Fork Fork中文名派生，作用就是把一个其它组织的仓库给完全复刻到个人帐号下面。派生后，两个仓库之间就没有关系了。如还需要同步原仓库代码，可以通过反向PR（似乎Gogs不提供，Github有），进行代码合并；或创建一个新的远端地址，指向原仓库。如下图所示：Fork 在某些场景下十分有用。在单身网站Github上有太多的开源项目，当我们对某一个进行派生后，后面发现了一个BUG，并对其进行了修改，你就可以创建PR到原仓库（对方开启PR权限的情况下），原仓库拥有者看到后，经考虑，对方就可以进行合并操作，你的代码就进入了原仓库。这也就是所谓的开源社区贡献。如下图所示： ​ 以上的功能或特性其实在一些工作流中都有用到。 当前流行的Git工作流： Git Flow Feature Branch Flow Forking Flow Git Flow: ​ 在这种工作流中会长期存在两个分支，分别是 master 和 develop，同时还会存在其它类型的短期分支，如：feature, release, hotfix。下面来主要介绍下该种团队工作流方式，同时结合我们自己的工作流，进行相应的对比，以找出可以借鉴与需要改善的地方。 master分支： master分支只会保存稳定上线的代码，而不应该作为一个可以被随意合并的分支用。当对master分支进行PR合并**（master分支不应该被直接合并，而应该通过创建PR的方式进行合并（仍然视情况而定））操作的时候，也就代表着代码已经可以上线了，或者已经上过线了（此处的两种说法决定了合并代码与上线操作的顺序，后面讲）**。合并操作之后，即可进行打tag的操作， 也就是说master分支的代码应只用作tag分配版本号使用，不应做任何修改与直接合并用。 develop分支：develop分支是一个功能的集成分支，通常也叫开发分支。它应该始终大于等于master分支的提交，我们平时开发不应该在该分支进行开发，该分支只作为合并代码用，这也和上面master分支相呼应。既然不能在develop分支直接开发，那么就得存在一个额外的分支来进行开发，由此牵引出下面这个分支。 feature分支：由于 develop 分支始终大于等于 master 的提交，当产品来了不同的需求后，分配了不同的任务给不同的人之后，每个人都可以根据develop 创建一个新的 feature 分支出来，我们所有的需求开发都在 feature 分支上进行， feature 开发完成后，都应合并回 develop，也就是说 develop 上保存的代码至少应该是开发者认为比较准确的，经过一定自测的代码。当 develop 上有一个或多个 feature 后，开发者即可部署 develop 到测试环境（这里就看出和我们先有的区别，我们有一个test分支，而 Git Flow 是没有 test 分支的），测试人员进行测试，期间如有任何的改动，都应该在各自的 feature 中开发并合并回 develop，继续进行测试。在测试期间，可能会有额外的 feature 产生， 若 develop 正在被进行测试， 那么此时产生的额外的 feature 不应该 合并回 develop， 直到 develop 测试稳定后，即产生下面这个分支。 release分支， 该分支是基于 develop 开发出来， 只要切出了这个分支，即代表 develop 可以被合并了。develop 的代码又可以 继续往前走了。 release 分支，也叫 发布分支， 发布分支会进行继续测试或预发部署等操作，若有相应修改，应该在该分支进行。而不是 develop。 等到完全稳定后，该分支应合并到 master 去。 合并后即可进行 打 tag 的操作。 之后如何利用 jenkins上线，是基于 master 还是 基于 tag 都看各自的使用。 hotfix分支：hotfix 分支 是线上的BUG分支， 必须是基于 master 开出来， 修改完后，创建PR，直接测试该分支，测试完成后，通过PR合并到 master。 以上的和分支一旦合并到 master 后，都应该让 develop 保持与 master 的同步。以确保下次基于develop切出来的是最新的代码。 以上即是一个完整的 Git workflow 的流程。 Feature Branch Flow: ​ 通过讲解上面的 Git Flow，对不同类型的分支已经有一个概念。而这里讲的 Feature Branch Flow 实际上就是一个简化版的 Git Flow（并不是先有GitFlow 再有 Feature Branch Flow）。该工作流正常只有 master 和 feature 分支，每次开发新需求，只需要从master分支切出一个新的出来，开发完并进行测试 feature 后，进行合并到 master， 该流程即完成。看起来比较简单，而实际上这也是一个现在流行的做法。 Forking Flow: ​ 这种工作流在上面介绍Fork的概念与使用场景的时候已经提到了。总的来说就是团队开发者对中央仓库进行Fork操作，派生到各自帐号下后，即可进行不同需求的开发，当开发完成后，即可进行创建PR的操作。但这种方式在团队协作中应用的比较少，该方式也不利于测试，说他流行是因为，开发者对开源社区进行贡献，均采用此种方式进行合并代码。 当前弊端： 上线之后，由于我们现在实行的是先合并代码到 master，并且只会拉取 master 的代码进行部署，一旦出问题，就无法快速切回上一个稳定版本。 当前的工作流有些类似 Feature Branch Flow，但也有区别，当前还未利用起来Issue与Pull Request，同时多了一个test分支， 反而在意义上看来， test 分支 更像是 Feature Branch Flow 的 develop 分支（某些Feature Branch Flow会有develop分支，但该分支同样不应该被直接修改代码），因为一旦涉及到多版本开发的时候，我们的要求是 不能再合并 test 到 master，而是合并 deve/ 到 master，这也和 Feature Branch Flow 理念一样。 考虑改进： 考虑在 Jenkins 部署的时候，可以对 tag 或 分支 进行选择，以此来进行快速的切换线上的代码，保证最小化影响。 团队可以利用起来 Issue 和 Pull request， 该方式是能切实保证团队代码质量与规范流程的一大助力。 开发，测试 可以多建立几套环境，以应对QA需要同时测试几个不同的需求。类似之前做过的 lf, lf_p, lf_back这些。但这些不免让人觉得不太好记，可以考虑使用以数字为后缀的名字，同时不再以 Git hook 来进行代码部署，直接使用 Jenkins， 给每套环境都准备好相应的 Job，并提供对 待测试分支进行选择的功能，这样在几个环境的加持下，即使来了演示需求，紧急修复，紧急需求，再配合Git工作流 都可以从容应对。 ","link":"https://hzqyihui.github.io/post/git-fen-zhi-yu-gong-zuo-liu/"},{"title":"netstat命令：查看端口占用","content":"netstat -tunlp 用于显示 tcp，udp 的端口和进程等相关情况。 netstat 查看端口占用语法格式： -t (tcp) 仅显示tcp相关选项 -u (udp)仅显示udp相关选项 -n 拒绝显示别名，能显示数字的全部转化为数字 -l 仅列出在Listen(监听)的服务状态 -p 显示建立相关链接的程序名 ","link":"https://hzqyihui.github.io/post/netstat-ming-ling-cha-kan-duan-kou-zhan-yong/"},{"title":"nl命令：带行号查看文件","content":"nl -b a 文件， 查看文件的时候， 空行也会 带行号 ，类似 cat -n nl -b t 文件， 查看文件的时候， 空行不带 行号， 也可写作 nl 文件 ","link":"https://hzqyihui.github.io/post/nl-ming-ling-dai-xing-hao-cha-kan-wen-jian/"},{"title":"Git奇难杂症：","content":" 在 Win 10 下，git bash 使用 git status 后， 中文是数字的。看起来很难受 ，使用 解决。 参考链接： http://xstarcd.github.io/wiki/shell/git_chinese.html 2. Linux 下， 每次 git status 都看到一大堆改变，实际上又没有文件内容的变化，原因在于， 文件的 权限发生了变化， 解决此问题，则忽略文件的权限变化。 ","link":"https://hzqyihui.github.io/post/git-qi-nan-za-zheng/"},{"title":"Redis安装笔记","content":" 最简单的，直接使用 yum install redis 即可安装。 安装后， redis.conf 文件在 /etc 下。 修改配置文件，之后再启动。 这里列举下比较重要的配置项 配置项名称 配置项值范围 说明 daemonize yes、no yes表示启用守护进程，默认是no即不以守护进程方式运行。其中Windows系统下不支持启用守护进程方式运行 port 指定 Redis 监听端口，默认端口为 6379 bind 绑定的主机地址,如果需要设置远程访问则直接将这个属性备注下或者改为bind * 即可,这个属性和下面的protected-mode控制了是否可以远程访问 。 protected-mode yes 、no 保护模式，该模式控制外部网是否可以连接redis服务，默认是yes,所以默认我们外网是无法访问的，如需外网连接rendis服务则需要将此属性改为no。 timeout 300 当客户端闲置多长时间后关闭连接，如果指定为 0，表示关闭该功能 loglevel debug、verbose、notice、warning 日志级别，默认为 notice databases 16 设置数据库的数量，默认的数据库是0。整个通过客户端工具可以看得到 rdbcompression yes、no 指定存储至本地数据库时是否压缩数据，默认为 yes，Redis 采用 LZF 压缩，如果为了节省 CPU 时间，可以关闭该选项，但会导致数据库文件变的巨大。 dbfilename dump.rdb 指定本地数据库文件名，默认值为 dump.rdb dir 指定本地数据库存放目录 requirepass 设置 Redis 连接密码，如果配置了连接密码，客户端在连接 Redis 时需要通过 AUTH 命令提供密码，默认关闭 maxclients 0 设置同一时间最大客户端连接数，默认无限制，Redis 可以同时打开的客户端连接数为 Redis 进程可以打开的最大文件描述符数，如果设置 maxclients 0，表示不作限制。当客户端连接数到达限制时，Redis 会关闭新的连接并向客户端返回 max number of clients reached 错误信息。 maxmemory XXX 指定 Redis 最大内存限制，Redis 在启动时会把数据加载到内存中，达到最大内存后，Redis 会先尝试清除已到期或即将到期的 Key，当此方法处理 后，仍然到达最大内存设置，将无法再进行写入操作，但仍然可以进行读取操作。Redis 新的 vm 机制，会把 Key 存放内存，Value 会存放在 swap 区。配置项值范围列里XXX为数值。 这里我要将daemonize改为yes，不然我每次启动都得在redis-server命令后面加符号&amp;，不这样操作则只要回到Linux控制台则redis服务会自动关闭，同时也将bind注释，将protected-mode设置为no。 这样启动后我就可以在外网访问了。同时设置密码， 把requirepass 前面的 # 号给去掉。 ","link":"https://hzqyihui.github.io/post/redis-an-zhuang-bi-ji/"},{"title":"排序的稳定性含义：","content":"排序的稳定性含义： 排序的稳定，不是指某种排序算法有时候时间复杂度是 O(N^2)， 有时候是 O(N * Log N)， 而是指的，在原始序列中，相同的值，他在排序后， 相对位置 有没有发生改变。如果发生了改变了，则不稳定，如果没有发生改变，则稳定。 如 ： 2 3 7 1 2 7 3 排序后 为 1 2 2 3 3 7 7, 那么第一个2 是不是原来序列中的 第一个2， 以此来判断的排序的稳定性。 工程中的综合排序算法： 如果待排序的数组，是一堆 原始类型的数据，如 int, double, float ,string 等的话， 那么排序结果，就不需要关心，稳定性问题，相同值的相对次序，即使被 改变了，也无妨。那么 系统默认会用 快速排序。 若待排序数组是一顿对象类型的数据，数据中比如需要靠分数，或者班级等作为划分的话， 对稳定性要求比较高。则使用归并排序。 若样本量小于60（大概的一个数），就会直接使用插入排序， 因为在样本量很小的情况下， 常数项时间复杂度很低。则无所谓。 当使用归并排序的时候， 原始代码逻辑是 L==R，则返回， 但是这里可以混用。 即 R - 60 &lt;= L 的时候，就可以在内部用 插入排序排好了。 ","link":"https://hzqyihui.github.io/post/pai-xu-de-wen-ding-xing-han-yi/"},{"title":"希尔排序","content":"PHP版本： 暂时还没写Golang版本，之后补上 ","link":"https://hzqyihui.github.io/post/xi-er-pai-xu/"},{"title":"SSH开启无密码登录","content":"开启无密码登录： 第一次安装ssh后， 这时本地还没有自己的公钥和私钥，需要使用命令： ssh-keygen 生成公钥和私钥。 生成后的公钥就可以做为一系列的用法了，比如在这个机器上去github 或者 自建的 gogs 上拉取和上传代码。 只需要把公钥放在github服务器上就可以了。若需登录其他的机器。则可以使用 回车后输入密码（仅该次需要输入），成功后，以后登录 ip 对应的机器，就不再需要密码了。 其实就是在Ip对应服务器上 max用户文件夹下，.ssh文件夹中生成 authorized_keys 文件。 ","link":"https://hzqyihui.github.io/post/ssh-kai-qi-wu-mi-ma-deng-lu/"},{"title":"改进版快速排序","content":"PHP版本： Golang版本： ","link":"https://hzqyihui.github.io/post/gai-jin-ban-kuai-su-pai-xu/"},{"title":"冒泡排序","content":"PHP版本： Golang版本： ","link":"https://hzqyihui.github.io/post/mou-pao-pai-xu/"},{"title":"堆排序","content":"PHP版本： Golang版本： ","link":"https://hzqyihui.github.io/post/dui-pai-xu/"},{"title":"选择排序","content":"PHP版本： Golang版本： ","link":"https://hzqyihui.github.io/post/xuan-ze-pai-xu/"},{"title":"归并排序","content":"PHP版本： Golang版本： ","link":"https://hzqyihui.github.io/post/gui-bing-pai-xu/"},{"title":"位运算计算中间值：","content":"计算中间值： 一般情况下，我们计算中间值都是使用： 得出结果。但是这样的情况，就有可能产生内存溢出的问题的，假如 L 和 R 都很大，他们本身不溢出，但是相加后，就可能溢出了。 所以我们可以改写成： ，上面的算式和 原始算式，实际上都是一样的，但是这样就不会产生溢出了。因为L本身既然存在，那么它本身不是溢出的， R - L就更加不会溢出了， 除以2后， 再加上原始L 也就不会溢出了。 我们还可以改写： 即 ， R - L 的值 右移一位， 右移一位，即代表着 除以2， 而位运算，是比算数运算快得多的。所以 这也是一种优化的方式。 那么 右移 代表 除以 2， 同理 左移 就代表乘以 2。 左移，就是在操作数 二进制化 后 的 右边补充0； 右移，如果原始数 二进制化 后最右边是1， 则右移后直接删除。 ","link":"https://hzqyihui.github.io/post/wei-yun-suan-ji-suan-zhong-jian-zhi/"},{"title":"递归的时间复杂度：","content":"递归的时间复杂度： 在数组中找出最大数的递归算法如下： 剖析递归行为和递归行为时间复杂度的估算 一个递归行为的例子 master公式的使用 ","link":"https://hzqyihui.github.io/post/di-gui-de-shi-jian-fu-za-du/"},{"title":"对数器","content":"对数器： 以上即是对数器，总的来说就是， 用一个绝对正确的方法与自己写的作对比， 并且用大量的随机样本进行测试。 对数器的概念和使用 有一个你想要测的方法a， 实现一个绝对正确但是复杂度不好的方法b， 实现一个随机样本产生器 实现比对的方法 把方法a和方法b比对很多次来验证方法a是否正确。 如果有一个样本使得比对出错，打印样本分析是哪个方法出 错 当样本数量很多时比对测试依然正确，可以确定方法a已经 正确。 ","link":"https://hzqyihui.github.io/post/dui-shu-qi/"},{"title":"stat命令： 查看文件的修改","content":"stat命令： 查看文件的详细信息 为方便记忆， stat 可当做 status的缩写， 实际上就是查看文件的状态，也就是文件的详细信息。 使用 从图中看出， Access 代表最后一次的访问时间（仅是访问，不是修改） Modify 代表最后一次修改文件的时间 Change 代表最后一次修改文件属性的时间，如修改文件权限，文件大小等。 当我使用 cat 去查看文件的时候， Access Time 会跟着改变。 当我修改文件的时候，三个时间都会跟着改变， 当我修改文件的权限的时候， Change Time 会跟着改变， 如 chmod 777 从上面来看， 没法查到文件的创建时间， 有时候我想知道文件的创建时间， 这个时候就需要更细的去查看了。 文件的创建时间被存储在EXT4文件系统inode中。 EXT文件系统的早期版本不支持文件的创建时间。 有一个“ crtime “（创建时间）时间戳 debugfs统计输出。最后EXT4支持创建时间就像windows的 NTFS中的 “btime “一样。 按照以下如何找到文件的创建时间的说明。选择一个现有的文件或创建新的文件测试。在这个例子中，我使用现有的文件。 (只针对EXT4文件系统) 比如 ext4、xfs、btrfs 都支持，zfs、vfat、ntfs 不支持。 第1步：查找文件的inode编号 查找使用下面的命令终端的任何文件的inode号。 第2步：查找文件所在的分区或VG 从上面可以看出， /dev/mapper/developer--vg-root 被挂载 为 / ，create.txt 在 / 下面。 第3步：查找文件文件创建时间 输入以上命令后， 即可得到 具体的信息，如下所示： 上面结果中的 crtime 即为 该文件的创建时间。 ","link":"https://hzqyihui.github.io/post/stat-ming-ling-cha-kan-wen-jian-de-xiu-gai/"},{"title":"Windows快捷键","content":" 快捷键 截图 ","link":"https://hzqyihui.github.io/post/windows-kuai-jie-jian/"},{"title":"各类查找命令：","content":"各类查找命令： 1.1 find命令 Linux find 命令是所有 Linux 命令中最有用的一个，同时也是最混乱的一个。它很难，因为它的语法与其他 Linux 命令的标准语法不同。但是，它很强大，因为它允许您按文件名、文件类型、用户甚至是时间戳查找文件。使用 find 命令，您不但可以找到具这些属性任意组合的文件，还可以对它找到的文件执行操作。 1.2 locate命令 Linux locate命令用于查找符合条件的文档，他会去保存文档和目录名称的数据库内，查找合乎范本样式条件的文档或目录。locate 让使用者可以很快速的搜寻档案系统内是否有指定的档案。其方法是先建立一个包括系统内所有档案名称及路径的数据库，之后当寻找时就只需查询这个数据库，而不必实际深入档案系统之中了。在一般的 distribution 之中，数据库的建立都被放在 crontab 中自动执行。 1.3 grep命令 Linux grep命令用于查找文件里符合条件的字符串。grep也可以根据文件名查找文件，但一般用于查找文件内的内容。 grep（global search regular expression(RE) and print out the line，全面搜索正则表达式并把行打印出来）是一种强大的文本搜索工具，它能使用正则表达式搜索文本，并把匹配的行打印出来。 1.4 whereis命令 whereis命令只能用于程序名的搜索，而且只搜索二进制文件（参数-b）、man说明文件（参数-m）和源代码文件（参数-s）。如果省略参数，则返回所有信息。 1.5 which命令 which命令的作用是，在PATH变量指定的路径中，搜索某个系统命令的位置，并且返回第一个搜索结果。也就是说，使用which命令，就可以看到某个系统命令是否存在，以及执行的到底是哪一个位置的命令。 1.6 type命令 type命令其实不能算查找命令，它是用来区分某个命令到底是由shell自带的，还是由shell外部的独立二进制文件提供的。如果一个命令是外部命令，那么使用-p参数，会显示该命令的路径，相当于which命令。 1.7 总结 ","link":"https://hzqyihui.github.io/post/ge-lei-cha-zhao-ming-ling/"},{"title":"Go进阶知识点","content":" 面试题： https://developer.aliyun.com/article/740219?utm_content=g_1000095344 go struct 能不能比较 因为是强类型语言，所以不同类型的结构不能作比较 ，但是同一类型的实例值是可以比较的，实例不可以比较，因为是指针 类型。 3. go defer（for defer），先进后出，后进先出 select可以用于什么，常用于 gorotine 的完美退出 context包的用途 client如何实现长连接 主协程如何等其余协程完再操作 slice，len，cap，共享，扩容 map如何顺序读取 RPC调用的含义： https://mp.weixin.qq.com/s/9GZtmV4HIgg0o2OcZiqXgw 在Go中堆栈的含义。 堆内存：堆内存由运行时申请（也叫程序员申请），是在整个运行过程中，进行动态分配的，由内存分配器分配，由垃圾回收器回收。堆内存是不连续的，所以容易形成碎片。 栈内存：栈内存由编译器分配和释放，开发者无法控制（这就是调用栈）。保存了函数的参数，局部变量，函数调用栈帧等信息，随着函数的调用而创建，对着函数的返回而销毁。栈内存是连续的。 整个运行过程中，堆内存只有一份，栈内存是每个协程独立的。 线程栈空间，初始大小为2MB，协程栈空间初始值为2KB,最大为1GB。 参考地址：https://blog.csdn.net/wohu1104/article/details/113815428 sync.WaitGroup 作用， 主要是为了解决，如果想要在开启多个协程中做一些事情，并等待其处理完成，则可以使用。否则会出现 开启的协程还没来得及执行，主协程已经关闭，主协程关闭会把所有创建的其他协程一起结束 GMP的调度原理，一定要看 interface{} 作为参数类型的时候有的坑 ","link":"https://hzqyihui.github.io/post/go-jin-jie-zhi-shi-dian/"},{"title":"date命令：","content":"使用 date 可以直接查看当前日期。 若想要得到一个日期字符串所代表的秒数，则 举例如下： ","link":"https://hzqyihui.github.io/post/date-ming-ling/"},{"title":"find 命令：","content":"find 命令： 如： 参考： https://www.runoob.com/linux/linux-comm-find.html option的类型,expression 中可使用的选项有二三十个之多，在此只介绍最常用的部份。 根据文件内容来找文件： -exec 参数后面跟的是 command命令，注意点如下： command命令的终止，使用 ';' (分号）来判定，在后面必须有一个 ';' '{}'，使用{}来表示文件名，也就是find前面处理过程中过滤出来的文件，用于command命令进行处理 特别强调，对于不同的系统，直接使用分号可能会有不同的意义， 使用转义符 ''在分号前明确说明，对于前面我们遇到的问题，主要就是这个原因引起的！ ","link":"https://hzqyihui.github.io/post/find-ming-ling/"},{"title":"Linux进程，内存查看","content":"进程，内存查看 可用于查看某个进程的CPU，内存占用等 查看进程信息： 以上两种都对，但是输出的格式不同， 可以通过 man ps 命令来查看具体的解析细节。 查看进行运行的完整路径方法。 通过ps及top命令查看进程信息时，只能查到相对路径，查不到的进程的详细信息，如绝对路径等。这时，我们需要通过以下的方法来查看进程的详细信息： Linux系统使用的GNU ps命令支持3种不同类型的命令行参数 BSD 风格的参数，前面不加破折线 Unix 风格的参数，前面加单破折线 GNU 风格的长参数，前面加双破折线 STAT进程状态(第一个字符) R (Running): 该程序正在运行中 S (Sleep): 该程序目前正在睡眠状态 (idle), 但可以被唤醒(signal) D 不可被唤醒的睡眠状态 , 通常进程可能在等待 I/O 的情况 T 停止状态 (stop), 可能是在工作控制 ( 背景暂停 ) 或除错(traced) 状态 Z (Zombie): 僵尸状态 , 程序已经终止但却无法被移除至内存外 STAT进程状态(第二个字符) 1、 ps l 长格式输出 参数解释 2、 ps -ef 输出 参数解释 3、 ps -au(x) 输出 其代表含义如下：USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND ","link":"https://hzqyihui.github.io/post/linux-jin-cheng-nei-cun-cha-kan/"},{"title":"Go基础知识点","content":"基础知识点： 数组 array ， 切片 slice 数组在赋值后，不可以对整体进行更改， 但可以对 其中某个值进行更改。 slice 我认为就像是一个可变的数组， 可以对切片进行整体更改。 2. iota， 叫常量计数器，在 const 关键字出现时，会被重置为0。 注意， 每出现一个 const，iota 都会被重置为0。const中每新增一行常量声明将使 iota 计数一次。 new(T) 和 make(T,args) 的区别， new是为新值分配已置零的内存空间， 并返回指针 ，即类型为 *T 的值。换句话说，就是返回一个指针，该指针指向新分配的，类型为T的零值。适用于值类型，如数组，结构体等。重点：分配内存，并置零。 make 返回初始化后T类型的值，这个值并不是 T 类型的零值，也不是指针 *T ，是经过初始化之后的 T的引用。make() 只适用于 slice、map 和 channel. 结构体比较： 结构体只能比较是否相等，不能比较大小。相同类型结构体才能比较，何为相同，属性类型要一样，且属性顺序也得一样。 slice，map，func 是不能比较的 删除 map 中不存在的键时，不会报错，相当于不起任何作用。获取不存在的键时，返回值类型对应的零值。 类型断言怎么理解：接口值变量才能进行类型断言。 https://www.zbpblog.com/blog-230.html go语言.pdf 里 第十九天，第一题还有疑问。 第三十天，第一题疑问。 第四十三天，第一题疑问。 第五十二天，第一题疑问。 第五十四天，第二题疑问。 第五十六天，第二题，需要再看看。 57-2， 60-2 有问题。 63-2问题，匿名函数 64-2问题。 66-1问题。 67问题， 71问题， 74问题， 78问题。 81-2，83-2，90-2，92-2，100-2，104-1 for range ， 对数据进行迭代的时候，如果 被迭代对象是个数组，由于go语言是值传递，所以被复制了一份，即使for range 内部对其进行修改，也不会影响到原数组。但如果迭代对象是切片，由于切片本身是 指向底层数组的指针，即使复制一份，该指针仍然指向底层数组，内部进行修改，会影响到原切片。map也是，是指针类型的，所以对map进行delete 都会影响原map Go语言里没有 ++i ，--i 的写法， 只有 i++ 和 i-- 的写法。 常量不同于变量在运行期分配内存，常量通常会被编译器在预处理阶段直接展开，作为指令数据使用，所以常量无法寻址。 异常的触发：空指针解析，下标越界，除数为0，调用panic函数。 有方向的 channel 不可以被关闭。 在Go里，函数内定义的变量未使用，是会编译错误的，参数可以不使用，不会报错。常量由于是编译期间就被编译成指令，不使用，也不会报错。 常量组中如不指定类型和初始化值，则与上一行非空常量右值相同 变量隐藏： PHP 的按位取反，是 ~ 符号， 如 a= a = ~a= a; ， 但是 Go里面是 a := ^2 , 但是 ^ 符号在 二元运算符是 a := 2 ^ 3， 按位异或的意思， 相同为0，相异为1， 给大家重点介绍下这个操作符 &amp;^ ，按位置零，例如： z = x &amp;^ y ，表示如果 y 中的 bit 位为 1，则 z 对应 bit 位为 0，否则 z 对应 bit 位等于 x 中相应的 bit 位的值。 不知道大家发现没有，我们还可以这样理解或操作符 | ，表达式 z = x | y ，如果 y 中的 bit 位为 1，则 z 对应 bit 位为 1，否则 z 对应 bit 位等于 x 中相应的 bit 位的值，与 &amp;^ 完全相反。 这种写法才能正确取出字符的个数。 string的底层是byte，byte与rune的不同之处是： byte 等同于int8，常用来处理ascii字符rune 等同于int32,常用来处理unicode或utf-8字符 或者可以这样说： rune 能操作任何字符byte 不支持中文的操作 函数中传参 map slice channel， 内部改变后，还是会修改原值。 闭包函数如果不传参，内部的变量是对外部变量的引用，外部变量值发生变化，内部也会。 除非通过参数传参进入。 recover() 必须在 defer() 函数中直接调用才有效。无效的：直接调用recover()、在 defer() 中直接调用 recover() 和 defer() 调用时多层嵌套。 匿名函数和 defer ， 还有 带命名的参数一起用时，需要格外注意： https://mp.weixin.qq.com/s/Hm8MdrqYgCQPQ4A1nrv4sw 23.可变函数是指针传递。 24：分号插入规则： https://gfw.go101.org/article/line-break-rules.html ","link":"https://hzqyihui.github.io/post/go-ji-chu-zhi-shi-dian/"},{"title":"tree命令","content":"tree命令 默认情况下， Debian系列系统，都没有安装tree命令， 且无法通过 apt-get来安装 tree。 参数： 在项目目录下执行 go build 会生成可执行文件， 如果使用 go install 会，在GOPATH下的/bin目录下生成可执行文件（没有配置GOBIN）， 实际上就是比 go build 多执行了一个操作，移动可执行文件到 /bin目录 ","link":"https://hzqyihui.github.io/post/tree-ming-ling/"},{"title":"GOROOT, GOPATH","content":" GOROOT， 如果安装go后，linux没有这个变量，那么一般设置为安装GO目录下的go文件夹。如 /usr/local/go 把上面一句放在 .bashrc 或者 .zshrc文件中。最后 source生效即可。 使用 go env 可以看到安装go后，关于GO的 一些环境变量值，但是这些有些是 默认值， 在使用 echo $GOROOT 等可能看不到具体的值， 就是因为没有在 系统环境变量中加入 GOROOT的具体值。 但是能通过 go env GOROOT 看到具体值 GOPATH GOPATH 就是实际上可以看成是工作目录， 不同于Java等其他语言，项目存在于任意目录均可。 GO项目必须 在 已配置的 GOPATH 路径下。当然，可以多个路径， 使用分号隔开，即可。需要注意的是， 当使用 go get 来下载包的时候，默认会下载在第一个GOPATH路径下。 如果Go 版本在 1.12之前， 建一个Go项目的话，应该要注意 3.1 一定要将项目代码放在GOPATH下的src目录下 3.2 要记得设置 GO111MODULE=off 才行。 3.3 包查找方式，先查找 gopath/src 这个目录下的包是否有， 没有再去找 goroot/src 目录下去找 如果Go版本在1.12之后，则要注意 4.1 自动或手动建一个go.mod 文件， 4.2 设置 GO111MODULE=on ","link":"https://hzqyihui.github.io/post/goroot-gopath/"},{"title":"locate 命令：","content":"locate命令用于查找文件，它比find命令的搜索速度快，但是它需要一个数据库，这个数据库一般是 crontab 每天调用一次 updatedb 命令来更新的. 因此你只要执行 updatedb 这个命令就可以了, 当然这个命令操作的数据库需要root权限. ","link":"https://hzqyihui.github.io/post/locate-ming-ling/"},{"title":"Nginx 安装","content":"Nginx 安装： 可参考： https://www.nginx.com/resources/wiki/start/topics/tutorials/install/ 安装完成后， 配置连接PHP的时候会遇到一些错误， 在NGINX 的 PHP的配置相关的地方， $document_root 代表的就是 /home/max 在配置PHP-fpm的配置时， 有需要注意的地方。 可参考： https://snowstar.org/2017/12/11/nginx-with-php-and-path-info/ 配置完成后，网站样式文件不在了，此时需要修改 可参考： http://www.zhai14.com/blog/notes-of-nginx-configuration-on-location.html 大于2M的 文件上传， 会报错 413 nginx 出现413 Request Entity Too Large问题的解决方法 PHP配置 ","link":"https://hzqyihui.github.io/post/nginx-an-zhuang/"},{"title":"PHP 知识点分享","content":"PHP 知识点分享： 字符串的使用 可将字符串当作一个字符的集合来使用，可独立访问每个字符。仅适用于单字节字符（字母、数字、半角标点符号），像中文等不可用（utf8下，中文3字节表示） $str = &quot;abcd&quot;; echo $str[3]; // d echo str0;//a，php7.4已废弃这种方式//另一种方式也可以使用strsplit把所有字符分割成数组。str{0}; // a， php7.4 已废弃 {} 这种方式 //另一种方式 也可以使用str_split把所有字符分割成数组。 str0;//a，php7.4已废弃这种方式//另一种方式也可以使用strs​plit把所有字符分割成数组。strArr = str_spilt(str);//同样不适合中文Array([0]=&gt;a[1]=&gt;b[2]=&gt;c[3]=&gt;d)functionmbstrsplit(str); //同样不适合中文 Array ( [0] =&gt; a [1] =&gt; b [2] =&gt; c [3] =&gt; d ) function mb_str_split(str);//同样不适合中文Array([0]=&gt;a[1]=&gt;b[2]=&gt;c[3]=&gt;d)functionmbs​trs​plit(str){ return preg_split('/(?&lt;!^)(?!$)/u', str ); //反向预搜索， 正向预搜索 } str='博客'; mb_str_split($str); 打印结果如下： Array ( [0] =&gt; 博 [1] =&gt; 客 ) 转换类型 平时 我们知道在变量前，加上(int), (string), (float) 之类的即可进行显示转换，下面介绍一种，可直接转换为null的 转换，。 (unset) //转换为NULL a=′a′;a = &#x27;a&#x27;; a=′a′;a = (unset)a;//此时a; //此时a;//此时a 为 NULL //换种方式 a=′a′;unset(a = &#x27;a&#x27;; unset(a=′a′;unset(a); dd($a); //报错 PHP Notice: Undefined variable: a in test.php on line 39 Notice: Undefined variable: a in test.php on line 39 isset 和 empty, is_null 对变量的检测 3.1 isset 要检测变量不存在则， 返回 false； 但需要注意，变量是否本身为 Null值， 如果是，也返回 false； 都不满足则返回true了 。 为了更方便检测变量是否存在，可使用 建议用array_key_exists判断 a=null;a = null; a=null;varExists = array_key_exists('a', get_defined_vars()); if($varExists){ echo '变量a是存在的'; } 3.2 empty， 检测变量不存在，与各个空值，0值都为true， 否则为 false 3.3 is_null 当参数满足下面三种情况时，is_null()将返回TRUE，其它的情况就是FALSE 1、它被赋值为NULL 2、它还没有赋值 3、它未定义，相当于unset(),将一个变量unset()后，不就是没有定义吗 static 的部分用法 我们都知道 static 可以直接定义静态变量和 静态方法， 无论是调用静态变量和静态方法， 用 :: 即可调用， 如果是本类，则使用self 调用， 如果是在类 外部，则使用类名进行调用， 但这里对其不进行过多赘述，这些都是比较常用。下面讲下不常用的。 static在类中的延迟静态绑定； 延迟静态绑定是指允许在一个静态继承的上下文中引用被调用类。 延迟绑定的意思为：static::不再为定义当前方法所在的类，而是实际运行时所在的类。 (总的意思就是子类继承了父类后，父类中使用了一个静态方法或变量，如static::A()， 但方法A不存在于父类中， 存在于子类中，这样写是正确的，不会因为方法不存在而报错，因为static 修饰的A 会在执行过程中，绑定到具体的子类 上，只要保证子类有此方法即可) 注：它可以用于(但不限于)静态方法的调用。 self::，代表本类(当前代码所在类) 永远代表本类，因为在类编译时已经被确定。 即，子类调用父类方法，self却不代表调用的子类。 parent:: 代表父类 static::，代表本类(调用该方法的类) 用于在继承范围内引用静态调用的类。 运行时，才确定代表的类。 static::不再被解析为定义当前方法所在的类，而是在实际运行时计算的。 除了简单的static延迟绑定的用法，还有一种转发调用， forward_static_call_array()(该函数只能在方法中调用)将转发调用信息（不过多赘述，类似于本节的第8点，PHP反射）， 概括实例如下： class A { public static function fooStatic() { static::who(); } public static function who() { echo CLASS.&quot;aaa\\n&quot;; } } class B extends A { public static function testStatic() { A::fooStatic(); B::fooStatic(); C::fooStatic(); parent::fooStatic(); self::fooStatic(); } public static function who() { echo CLASS.&quot;bbb\\n&quot;; } } class C extends B { public static function who() { echo CLASS.&quot;ccc\\n&quot;; } } C::testStatic(); 大家一起来得到答案： array_merge 和 + ， 合并数组 5.1 array_merge 合并两个或多个数组的时候， 如果键名是非数字，则会自动合并到一起，但若出现重复的键名，后面的会覆盖前面的。 若键名为数字，其会根据前一个数组的索引值，自动给后面的更改索引值为前面的递增，也就是说会改变原有的键名。 5.2 + 合并数组， 使用+ 号 合并数组，键名相同的情况下，最先出现的键名会被保留， 并且即使键名是数字，也不会被重新编排，这个特性 对部分业务场景会非常有用。 5.3 array_push, 仅仅是把对应值完全放到数组末尾， 原来的结构会被保留，整个作为一个新的值， 键名跟着前一个递增。这与array_merge 有所区别 5.4 array_combine， 两个参数， 第一个参数，作为新数组的键， 第二个参数作为新数组的值。 并不是给原有数组增添新值。所以其实他不同于 上面三个的作用。 光从名字上看容易混淆。 a=[′a′,′b′,′c′];a = [&#x27;a&#x27;, &#x27;b&#x27;, &#x27;c&#x27;]; a=[′a′,′b′,′c′];b = ['5' =&gt; 'test']; $c = [1, 2, 3]; arrayMerge=arraymerge(arrayMerge = array_merge(arrayMerge=arraym​erge(a, b);b); b);arrayPlus = $a + b;b; b;arrayCombine = array_combine($a, c);arraypush(c); array_push(c);arrayp​ush(a, $b); // $arrayMerge Array ( [0] =&gt; a [1] =&gt; b [2] =&gt; c [3] =&gt; test ) // $arrayPlus Array ( [0] =&gt; a [1] =&gt; b [2] =&gt; c [5] =&gt; test ) // $arrayCombine Array ( [a] =&gt; 1 [b] =&gt; 2 [c] =&gt; 3 ) // array_push Array ( [0] =&gt; a [1] =&gt; b [2] =&gt; c [3] =&gt; Array ( [5] =&gt; test ) ) 指针 数组的内部指针： current/pos 返回当前被内部指针指向的数组单元的值，并不移动指针。 key 返回数组中当前单元的键名，并不移动指针 next 将数组中的内部指针向前移动一位，并返回移动后当前单元的值。先移动，再取值。 prev 将数组的内部指针倒回一位，并返回移动后当前单元的值先移动，再取值。 end 将数组的内部指针指向最后一个单元，并返回最后一个单元的值 reset 将数组的内部指针指向第一个单元，并返回第一个数组单元的值 each 返回数组中当前的键/值对并将数组指针向前移动一步。 返回的是一个由键和值组成的长度为4的数组，下标0和key表示键，下标1和value表示值 在执行each()之后，数组指针将停留在数组中的下一个单元或者当碰到数组结尾时停留在最后一个单元。如果要再用 each 遍历数组，必须使用 reset()。 以上指针操作函数，除了key()，若指针移出数组，则返回false。而key()移出则返回null。 若指针非法，不能进行next/prev操作，能进行reset/end操作 current/next/prev 若遇到包含空单元（0或&quot;&quot;）也会返回false。而each不会！ list 把数组中的值赋给一些变量。list()是语言结构，不是函数。 仅能用于数字索引的数组并假定数字索引从0开始 /* 可用于交换多个变量的值 */ list($a, b)=array(b) = array(b)=array(b, a);例：list(a); 例：list(a);例：list(drink, , $power) = array('coffee', 'brown', 'caffeine'); 复制数组，其指针位置也会被复制。 特例：如果数组指针非法，则拷贝的数组指针会重置，而原数组的指针不变。 【指针问题】 谁第一个进行写操作，就会开辟一个新的值空间。与变量(数组变量)值传递给谁无关。 数组函数current()被定义为写操作，故会出现问题。 foreach遍历的是数组的拷贝，当被写时，才会开辟一个新的值空间。 即，foreach循环体对原数组进行写操作时，才会出现指针问题。 如果开辟新空间时指针非法，则会初始化指针。 如果指针位置出现问题，则reset()初始化一下就可解决。 序列化（串行化） 数据传输均是字符串类型 除了资源类型，均可序列化 序列化在存放数据时，会存放数据本身，也会存放数据类型 作用：1.在网络传输数据时；2.为了将数组或对象放在磁盘时 序列化 serialize 产生一个可存储的值的表示 string serialize ( mixed $value ) 返回字符串，此字符串包含了表示value的字节流，可以存储于任何地方。 有利于存储或传递 PHP 的值，同时不丢失其类型和结构。 反序列化 unserialize 从已存储的表示中创建PHP的值 mixed unserialize ( string $str [, string $callback ] ) 对单一的已序列化的变量进行操作，将其转换回PHP的值。 8 . PHP反射 有时候我们我们为了能建立更好的代码结构，并能复用代码，就需要用到一些语言的特性来帮助我们完成任务，比如我们这次新建的仓库 trinity_middle_service， 我们需要利用PHP自带函数， 如call_user_func_array， 即可简单实现分发。使用方式如下： class Order{ public function getBasicInfo($orderId, write = false) { } } detailServiceInstance = new Order(); functionName=′getBasicInfo′;functionName = &#x27;getBasicInfo&#x27;; functionName=′getBasicInfo′;params[0] = 1000001; //或 params[′orderId′]=1000001params[&#x27;orderId&#x27;] = 1000001 params[′orderId′]=1000001params[1] = true; //或 params[′write′]=truecalluserfuncarray([params[&#x27;write&#x27;] = true call_user_func_array([params[′write′]=truecallu​serf​unca​rray([detailServiceInstance, $functionName], params);从以上代码可看出，calluserfuncarray第一个参数是个数组，该数组中0下标的值为一个类的实例，1下标的是这个实例中存在的方法；第二个参数即对应函数需要的参数了，该参数为数组形式，calluserfuncarray会自动按顺序对应每一个参数，所以在这里，参数的顺序很重要，无论是使用数字键名或字符键名，都必须一一对应函数具体的参数，这样才能正确实现函数的分发。8.1反射的应用使用calluserfuncarray，我们可以简单的实现分发，并且各个参数，也都很清晰。但是如果需要更加灵活的运用这个函数，我们还需要引入PHP的反射机制，如PHP自带的ReflectionMethod类，我们可以通过这个类，来直接获取到某个类中具体方法的各类信息，如参数个数，参数名，是否有默认值等等。代码如下：params); 从以上代码可看出， call_user_func_array 第一个参数是个数组， 该数组中0下标的值 为 一个类的实例， 1下标的 是这个实例中存在的方法； 第二个参数即 对应函数需要的参数了，该参数为数组形式，call_user_func_array 会自动 按顺序对应每一个参数，所以在这里，参数的顺序很重要，无论是使用数字键名或字符键名，都必须一一对应函数具体的参数，这样才能正确实现函数的分发。 8.1 反射的应用 使用call_user_func_array，我们可以简单的实现分发，并且各个参数，也都很清晰。但是如果需要更加灵活的运用这个函数，我们还需要引入PHP的反射机制，如 PHP自带的 ReflectionMethod 类， 我们可以通过这个类，来直接获取到某个类中具体方法的各类信息，如参数个数，参数名，是否有默认值等等。代码如下： params);从以上代码可看出，callu​serf​unca​rray第一个参数是个数组，该数组中0下标的值为一个类的实例，1下标的是这个实例中存在的方法；第二个参数即对应函数需要的参数了，该参数为数组形式，callu​serf​unca​rray会自动按顺序对应每一个参数，所以在这里，参数的顺序很重要，无论是使用数字键名或字符键名，都必须一一对应函数具体的参数，这样才能正确实现函数的分发。8.1反射的应用使用callu​serf​unca​rray，我们可以简单的实现分发，并且各个参数，也都很清晰。但是如果需要更加灵活的运用这个函数，我们还需要引入PHP的反射机制，如PHP自带的ReflectionMethod类，我们可以通过这个类，来直接获取到某个类中具体方法的各类信息，如参数个数，参数名，是否有默认值等等。代码如下：object = new Order(); functionName=′getBasicInfo′;functionName = &#x27;getBasicInfo&#x27;; functionName=′getBasicInfo′;reflectionObj = new \\ReflectionMethod($object, functionName);//通过以上的代码后，就可以直接通过functionName); //通过以上的代码后，就可以直接通过functionName);//通过以上的代码后，就可以直接通过reflectionObj获取相关信息了，如下 reflectionObj−&gt;isPublic();//可判断该方法是否为公有方法，还有isPrivate,isProtected,isStatic等等reflectionObj-&gt;isPublic(); //可判断该方法是否为公有方法， 还有isPrivate, isProtected, isStatic等等 reflectionObj−&gt;isPublic();//可判断该方法是否为公有方法，还有isPrivate,isProtected,isStatic等等reflectionObj-&gt;getParameters(); //可获取该方法所有参数，是个数组，可 foreach获取具体的参数 foreach($reflectionObj-&gt;getParameters() as arg) { if(array_key_exists(arg-&gt;name, $params)) { callParams[callParams[callParams[arg-&gt;name] = params[params[params[arg-&gt;name]; } else { paramVal=null;if(paramVal = null; if (paramVal=null;if(arg-&gt;isOptional()) { // 或使用isDefaultValueAvailable， 检测是否有可用的默认值 $paramVal = $arg-&gt;getDefaultValue(); } callParams[callParams[callParams[arg-&gt;name] = $paramVal; } } 具体参数： https://www.php.net/manual/zh/class.reflectionmethod.php 还有 ReflectionClass 可以获取一些类的信息，用法都是类似的。 8.2 调用不存在的方法 __call &amp; __callStatic (魔术方法) 当调用类中一个不存在或者没有权限访问的方法的时候，就会自动调用__call()方法。和__call对应静态方法的调用是__callStatic， 配合PHP反射，对代码进行优化，可对一些特殊的基础业务功能做到， 如工单, trinity 内部都是用了类似方法。 move_uploaded_file 该函数是前端文件上传文件到后台后可能用到的函数，上传后，我们会通过FILES[′file′][′tmpname′](文件上传后地址)，使用moveuploadedfile(_FILES[&#x27;file&#x27;][&#x27;tmp_name&#x27;](文件上传后地址)， 使用 move_uploaded_file(F​ILES[′file′][′tmpn​ame′](文件上传后地址)，使用moveu​ploadedf​ile(tmp_name, savePath);会把临时存到数据库的如：/tmp/phpwTJzUw文件给移动走，导致如果后面的代码还要用到savePath); 会把临时存到数据库的 如： /tmp/phpwTJzUw 文件给移动走，导致如果后面的代码还要用到savePath);会把临时存到数据库的如：/tmp/phpwTJzUw文件给移动走，导致如果后面的代码还要用到_FILES全局变量的时候，会出现找不到文件的问题。 引用传递 在使用array_pop等函数时， array_pop的参数 是引用传递，在编码过程中有时候会把 参数直接写成函数返回值，这样在 5.3以后是不允许的， 会报出致命错误（严格模式下）。 You will see: PHP Strict Standards: Only variables should be passed by reference in - on line 3 Strict Standards: Only variables should be passed by reference in - on line 3 d 以上来自于： https://www.php.net/manual/zh/function.array-pop.php 按引用传递参数的函数在被按值传递调用时行为发生改变. 此前函数将接受按值传递的参数, 现在将抛出致命错误. 之前任何期待传递引用但是在调用时传递了常量或者字面值 的函数, 需要在调用前改为将该值赋给一个变量。 以上来自于：PHP手册附录从PHP 5.2.x 移植到 PHP 5.3.x 部分 https://www.php.net/manual/zh/migration53.incompatible.php 而实际上看起来没有错， 只需要把那个函数返回值赋值给一个变量，再把变量当做参数传入就可以了。 如，在代码中经常会写到： error_reporting(E_STRICT); testArr=[′a′,′b′,′c′,′c′,′d′];testArr = [&#x27;a&#x27;,&#x27;b&#x27;,&#x27;c&#x27;,&#x27;c&#x27;,&#x27;d&#x27;]; testArr=[′a′,′b′,′c′,′c′,′d′];popValue = array_pop(array_unique(testArr));//以上代码会报错，必须使用testArr)); // 以上代码会报错，必须使用 testArr));//以上代码会报错，必须使用testArr = ['a','b','c','c','d']; testArr=arrayunique(testArr = array_unique(testArr=arrayu​nique(testArr) popValue=arraypop(popValue = array_pop(popValue=arrayp​op(testArr); json_encode 11.1 有时候，开发的场景可能需要把数组转为对象给前端输出，但是PHP的索引数组，下标是数字，如果从零开始的话， 使用json_encode 是不会把这个数组给转化成对象的。比如： test=[0=&gt;&quot;未定义&quot;,1=&gt;&quot;男&quot;,2=&gt;&quot;女&quot;];echojsonencode(test = [0 =&gt; &quot;未定义&quot;, 1 =&gt; &quot;男&quot;, 2 =&gt; &quot;女&quot;]; echo json_encode(test=[0=&gt;&quot;未定义&quot;,1=&gt;&quot;男&quot;,2=&gt;&quot;女&quot;];echojsone​ncode(test); 以上代码会这样输出： 11.2 如果代码是这样的： test=[1=&gt;&quot;男&quot;,2=&gt;&quot;女&quot;];echojsonencode(test= [1 =&gt; &quot;男&quot;, 2 =&gt; &quot;女&quot;]; echo json_encode(test=[1=&gt;&quot;男&quot;,2=&gt;&quot;女&quot;];echojsone​ncode(test); 这样则会输出： 以上的输出，请注意符号， 请注意键值， 这说明在没有下标0的情况下， json_encode会自动把没有下标0的数组给转为对象。但是我认为这不友好，当不清楚json_encode有这个特性的开发人员使用的时候，会对这一现象感到奇怪。 11.3 于是我们可以这么写代码。 test = new \\ArrayObject([0 =&gt; &quot;未定义&quot;, 1 =&gt; &quot;男&quot;, 2 =&gt; &quot;女&quot;]); echo json_encode(test);die; 这一就会输出： 注意符号与键值，这样我们就确保即使有下标0，我们也能显示的把数组给转成对象后，再给json_encode了。 ArrayObject 是 SPL扩展库的内置函数，所以需要安装SPL扩展， 不过似乎现在都已经内置在PHP里了。 implode implode有很大的坑，在PHP 5.6的时候， 该函数的两个参数，是可以互换的。正常要求是，第一个参数是 分割字符串， 第二个是 数组。同时可以换过来。 PHP7，该坑被去掉了。只能是正常的传参了。 禁用cookie后， 如何才能继续使用session https://www.cnblogs.com/Renyi-Fan/p/11012086.html ","link":"https://hzqyihui.github.io/post/php-zhi-shi-dian-fen-xiang/"},{"title":"Zsh 插件安装：","content":"Zsh 相关知识点： 插件 zsh-autosuggestion 可以进行命令建议， 也就是输入几个命令字母后，会有相应的建议命令隐藏在背后， 可以使用右键。补全命令。 安装自动建议插件： 安装高亮插件 最后再在 .zshrc 加入对应的插件名， 再 source .zshrc 启用 即可 具体位置在 .zshrc 文件中 plugins=(git) 位置处， 只需要间隔空格，加入就可以了 安装 自动跳转 之后在 .zshrc 中加入 以下行，再source .zshrc即可。 安装完成后，zsh 的变量会完全覆盖 原来 /etc/profile里的环境变量， 导致某些特殊的变量不能用。 问题 shell又bash变为zsh，发现/etc/profile中设置的环境变量全部失效了 在终端source /etc/profile设置的环境变量生效，但是zsh的主题以及插件消失。 在终端source ~/.zshrc后，zsh的主题和插件恢复 重新启动终端，环境变量再次失效 推测问题原因 /etc/profile的生效期是用户登录的时候，生效对象是所有用户的所有shell。然而最新安装的shell（zsh）不在/etc/profile的生效对象内。所以在变更shell时环境变量失效 在终端source /etc/profile时，重新载入了/etc/profile，所以环境变量生效，但是在/etc/profile中执行了 source /etc/bash.bashrc导致zsh的样式和主题失效（可以查看/etc/profile的源代码） 在终端source ~/.zshrc后，zsh的主题和插件恢复，理所当然样式和插件恢复 由于在终端中source /etc/profile只是让本shell进程执行，相当于只是在本shell执行了环境变量，所以重启之后依旧失效 尝试把变量直接加在.zshrc文件中 可以解决。 很多时候，我们会进入某个git项目目录中， zsh 会自动读取当前目录的git信息，并展示出来。这会造成卡顿，我们可去掉。 若需要恢复，则设置为0即可。 ","link":"https://hzqyihui.github.io/post/zsh-cha-jian-an-zhuang/"},{"title":"Linux系统基本知识点","content":"系统知识点： 1. 修改主机名： Ubuntu: 在 /etc/hostname 文件中即可修改 2. 添加环境变量 三种添加环境变量的方法： 1、直接使用export命令： 比如： 命令export可以查看各个系统变量和路径，发现系统变量中PATH中多了设置的路径，增加了CLASSPATH变量，则设置成功 也可单个变量输出查看： 2、 修改/etc/profile文件 在/etc/profile文件末尾添加： 有人说也可以在/etc/profile.d/文件夹中添加个sh文件，/etc/profile文件似乎会自动读取/etc/profile.d/文件夹中的各个脚本文件，我还没试过。 另外需要注意： CLASSPATH中当前目录“.”不能丢，把当前目录丢掉也是常见的致命错误。 在设置环境变量时特别要注意不能把原来的值给覆盖掉了，这是一种常见的错误。 软件越装越多，环境变量越添越多，为了避免造成混乱，所以建议所有语句都添加在文件结尾，按软件的安装顺序添加。 3、 修改主目录下的隐藏文件./bashrc 修改方式与修改/etc/profile文件相同 source .bashrc使修改生效 3.修改当前的shell 查看一共有多少shell 查看当前使用的shell 清楚DNS 缓存 参考： https://cnzhx.net/blog/how-to-flush-dns-cache-in-linux-windows-mac/ 若Linux上时间不对， 可用以下命令更新时间 需要区分 ntpd 和 ntpdate 5.1 ntpd 是一个时间服务，它会平滑的同步时间，有自己的机制去同步服务器时间 5.2 ntpdate 是直接更新时间，属于一个时间的跃变，这对业务会很有影响，在自己玩的虚拟机上，到是可以使用 ntpdate 直接更新，不然都建议使用 ntpd 平滑更新 安全Selinux： 禁止ping： 临时禁Ping： 永久禁ping IPTABLES防火墙禁ping ","link":"https://hzqyihui.github.io/post/linux-xi-tong-ji-ben-zhi-shi-dian/"},{"title":"Linux操作提升效率","content":"命令行： 生活在 Bash shell 中，熟记以下快捷键，将极大的提高你的命令行操作效率。 编辑命令 重新执行命令 控制命令 友情提示： ","link":"https://hzqyihui.github.io/post/linux-cao-zuo-ti-sheng-xiao-lu/"},{"title":"Linux命令操作快捷键","content":"命令行： 生活在 Bash shell 中，熟记以下快捷键，将极大的提高你的命令行操作效率。 编辑命令 重新执行命令 控制命令 Bang (!) 命令 友情提示： ","link":"https://hzqyihui.github.io/post/linux-ming-ling-cao-zuo-kuai-jie-jian/"},{"title":"YAML语法","content":"YMAL语法： YAML是专门用作配置文件的语言，简洁而强大，远比Json方便。 特性： 大小写敏感 使用缩进表示层级关系 缩进时不允许使用Tab键，只允许使用空格。 缩进的空格数目不重要，只要相同层级的元素左侧对齐即可 和普通开发语言类似： #号代表，一行的注释 YAML支持的数据结构：三种： 对象：键值对的集合，又称为映射（mapping）/ 哈希（hashes） / 字典（dictionary） 数组：一组按次序排列的值，又称为序列（sequence） / 列表（list） 纯量（scalars）：单个的、不可再分的值 下面分别介绍使用方法： 2.1 对象： ","link":"https://hzqyihui.github.io/post/yaml-yu-fa/"},{"title":"VIM学习笔记：","content":"可通过官方文档来学习 VIM学习： 2.1 一般安装vim后，不是完整版的，只是vim-tiny，也就是精简版的， 若需要完整版的，需要安装 2.2 为了让vim更方便人使用，可以进入vim的配置文件中修改一些配置： 之后在文件中加入以下配置： 2.3 分屏: :split 和 vsplit. 下面是主要的命令，你可以使用VIM的帮助 :help split. 你可以参考本站以前的一篇文章VIM分屏。 分屏启动Vim 分屏 2.4 复制命令： 2.5 剪切命令： 2.6 粘贴命令： 2.7 撤销和 反撤销 (undo redo) 2.8 vim纵向模式： 2.9 自动缩进当前行指令 == 格式化当前光标接下来的8行 8= 格式化选定的行 v 选中需要格式化的代码段 = 备注： 2.10 查找与替换 2.10.1 作用范围： 删除与复制 同理： 2.11 ESC键可以 被 ctrl + [ 或者 ctrl + c 替换，作用是一样的。都是退出编辑模式，进入命令模式 VIMRC vimrc是vim的 全局配置文件，目录在 /etc/vim/vimrc ， :%!xxd 可以查看当前文件的十六进制编码 中文键盘速查表： 在Vim内部执行shell命令 列模式编辑最后一列 使用列块模式 &lt;Ctrl + v&gt; （如果macvim或者gvim则是&lt;Ctrl + q&gt;）进入列块可视模式， jj$ 先下移动两行然后跳到行尾； A; 进入插入模式输入分号； 回到普通模式完成插入。 可参考：https://jelly.jd.com/article/6006b1045b6c6a01506c87ce ","link":"https://hzqyihui.github.io/post/vim-xue-xi-bi-ji/"},{"title":"经典快速排序","content":"快速排序： 快速排序说起来也很简单：我认为三个关键词即可概括，1. 基准； 2. 简单排序； 3. 递归。 为什么说三个关键词即可概括，首先先简单介绍下，快排到底是怎么排序的， 首先对一个乱序的数组选择一个基准值，这个基准值，为了最好的时间和空间复杂度， 建议选择中位数（也就是中间大小的数） 根据这个中位数， 来循环遍历，比它小的组成一个数组，比他大的组成一个数组。 从第二歩能得到一个“左”数组，一个“右”数组，但是这只是其中一次循环， 为了能方便理解， 可以用递归，把“左”数组和“右”数组再分别调用本次所写函数，在函数末尾，把左，中，右组合起来，即可完成。 PHP版： Golang版： ","link":"https://hzqyihui.github.io/post/jing-dian-kuai-su-pai-xu/"},{"title":"crontab","content":"crontab 使用 cron的时候，我们经常会因为 某个命令运行时间太长，命令再次被启动时，会出现多进程。 可以使用flock, 如： 当多个进程可能会对同样的数据执行操作时，这些进程需要保证其它进程没有也在操作，以免损坏数据。 通常，这样的进程会使用一个「锁文件」，也就是建立一个文件来告诉别的进程自己在运行，如果检测到那个文件存在则认为有操作同样数据的进程在工作。这样的问题是，进程不小心意外死亡了，没有清理掉那个锁文件，那么只能由用户手动来清理了。 参数 实例 crontab运用flock防止重复执行 ","link":"https://hzqyihui.github.io/post/crontab/"},{"title":"环境变量etc profile文件解析：","content":"/etc/ profile文件解析：该文件对所有用户均有效 有时候我们在安装了一个软件之后，会得到一些可执行命令，可是这些可执行命令只在安装软件的目录下，我们要么直接使用绝对路径。要么就使用链接， 在/usr/local/bin 这些bin文件夹中，建立一个链接： ln -s /usr/local/go/bin /usr/local/bin 这样太复杂了 ，如果很多，都要建立，那就太费时间了， 于是我们需要了解 profile文件的作用。 Linux有个常量叫： PATH，直接输入执行后，得到当前系统，哪些路径是环境变量的路径：我们可以把新软件的Bin目录给加入进去：直接在/etc/profile文件最后一行加入：exportPATH=PATH，直接输入执行后，得到当前系统， 哪些路径是环境变量的路径： 我们可以把新软件的Bin目录给加入进去： 直接在/etc/profile文件最后一行加入： export PATH=PATH，直接输入执行后，得到当前系统，哪些路径是环境变量的路径：我们可以把新软件的Bin目录给加入进去：直接在/etc/profile文件最后一行加入：exportPATH=PATH:/usr/local/go/bin 这样的话， 新软件的bin目录直接就是环境变量路径了，直接可使用可执行命令了。 Linux环境变量PATH设置 参考资料： 博客峰子_仰望阳光的博文Linux中环境变量设置 博文/etc/profile文件详解 博文 Linux中profile、bashrc、bash_profile之间的区别和联系 相关背景 Linux是一个多用户的操作系统。每个用户登录系统后，都会有一个专用的运行环境。通常每个用户默认的环境都是相同的，这个默认环境实际上就是一组环境变量的定义。用户可以对自己的运行环境进行定制，其方法就是修改相应的系统环境变量。 相关文件介绍 /etc/profile:此文件为系统的每个用户设置环境信息，当用户第一次登录时，该文件被执行。在这里修改的内容是对所有用户起作用的。所以如果你有对/etc/profile有修改的话必须得重启系统，你的修改才会生效，此修改对每个用户都生效。 /etc/bashrc:为每一个运行 bash shell 的用户执行此文件。当 bash shell 被打开时，该文件被读取。如果你想对所有的使用bash的用户修改某个配置并在以后打开的bash都生效的话可以修改这个文件，修改这个文件不用重启，重新打开一个bash即可生效。 ~/.bash_profile: 每个用户都可使用该文件输入专用于自己使用的 shell 信息，当用户登录时，该文件仅仅执行一次。此文件类似于/etc/profile，也是需要需要重启才会生效，/etc/profile对所有用户生效，~/.bash_profile只对当前用户生效。 ~/.bashrc:该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该文件被读取.（每个用户都有一个.bashrc文件，在用户目录下，符号‘~’就表示用户目录） ~/.bash_logout:当每次退出系统(退出bash shell)时,执行该文件。 /etc/profile和/etc/bashrc都是系统级别的，修改后可以在所有用户中起作用；/.bash_profile、/.bashrc和~/.bash_logout都是用户级别的，修改后只会作用于当前用户。 带profile的文件都是需要重新进入用户时才会生效，带bashrc的则是打开新的shell时生效； 启动过程 执行顺序：/etc/profile -&gt; (~/.bash_profile | ~/.bash_login | ~/.profile) -&gt; ~/.bashrc -&gt; /etc/bashrc -&gt; ~/.bash_logout Linux环境变量相关命令 显示环境变量HOME $ echo HOME设置新的环境变量HELLOHOME 设置新的环境变量HELLO HOME设置新的环境变量HELLO export HELLO=&quot;Hello&quot; 显示所有环境变量 $ env 显示所有本地定义的Shell变量 ￥ set 清除环境变量 $ export TEST=&quot;test&quot; $ env|grep TEST #此时显示：TEST =test $ unset TESTTEST TEST env|grep TEST #此时已经没有显示了，说明没有对应的环境变量了 设置只读变量 readonly TEST 设置Linux环境变量 之前介绍的使用export命令设置环境变量是在命令行中直接执行，这样设置的环境变量在退出shell时就会失效。要想设置永久有效的环境变量就需要修改之前提到的文件。 PATH声明 PATH=$PATH:&lt;PATH 1&gt;:&lt;PATH 2&gt;:&lt;PATH 3&gt;:------: 你可以自己加上指定的路径，中间用冒号隔开 需要注意的是，最好不要把当前路径”./”放到PATH里，这样可能会受到意想不到的攻击。 举例：在/etc/profile文件中添加环境变量 特点：所有用户；永久有效；生效需要重新进入用户 root权限： vim /etc/profile export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib 要想修改完文件后就立即生效，可以在命令行中执行： source /etc/profile Source命令也称为“点命令”，也就是一个点符号（.）。source命令通常用于重新执行刚修改的初始化文件，使之立即生效，而不必注销并重新登录 常用环境变量 PATH 决定了shell将到哪些目录中寻找命令或程序 HOME 当前用户主目录 HISTSIZE 历史记录数 LOGNAME 当前用户的登录名 HOSTNAME 指主机的名称 SHELL 当前用户Shell类型 LANGUGE 语言相关的环境变量，多语言可以修改此环境变量 MAIL 当前用户的邮件存放目录 PS1 基本提示符，对于root用户是#，对于普通用户是$ 解决在/etc/porfile下设置环境变量以后zsh没有起效的问题 问题１： 今天在添加java的环境变量的时候，我在/etc/profile添加了环境变量，因为我使用的是zsh，在source /etc/profile以后，zsh的主题和插件都没有了，java -version以后出现旗标信息，以为可以了，但是没有主题就很难受，重启shell。java -version提示没有此命令，懵~ 推测问题原因: 由于在终端中source /etc/profile只是让本shell进程执行，相当于只是在本shell执行了环境变量，所以重启之后依旧失效 推测解决方法: 在~/.zshrc中添加source /etc/profile 重启shell 输入java -version 完美解决! 通过百度发现:zsh其实并不使用/etc/profile文件，而是使用/etc/zsh/下面的zshenv、zprofile、zshrc、zlogin文件，并以这个顺序进行加载。 所以推测还有一种方法，在/etc/zsh/zprofile下面export相应的环境变量. 问题２： 在后面的使用中，发现切换到root用户的时候，java的环境变量又不生效了。 解决办法: 在/root/.bashrc文件尾部添加： source /etc/profile ok，完美解决 ","link":"https://hzqyihui.github.io/post/huan-jing-bian-liang-etc-profile-wen-jian-jie-xi/"},{"title":"Linux etc sudoers文件说明：","content":"/etc/sudoers文件说明： 简介 sudo是linux下常用的允许普通用户使用超级用户权限的工具，允许系统管理员让普通用户执行一些或者全部的root命令，如 halt，reboot，su等等。这样不仅减少了root用户的登陆 和管理时间，同样也提高了安全性。Sudo不是对shell的一个代替，它是面向每个命令的。它的特性主要有这样几点： 2.简单解析： 一般来说，通过cat /etc/sudoers指令来查看该文件, 会看到如下几行代码: 对/etc/sudoers文件进行编辑的代码公式可以概括为: 为了方便说明, 将公式的各个部分称呼为字段1 - 字段5: 在上面的默认例子中, &quot;字段1&quot;不以%号开头的表示&quot;将要授权的用户&quot;, 比如例子中的root； 代表：表示: 普通用户 max 在主机(或主机组)mycomputer上, 可以通过sudo执行reboot和shutdown两个命令。&quot;字段3&quot;和&quot;字段4&quot;省略。 &quot;字段3&quot;如果省略, 相当于(root:root)，表示可以通过sudo提权到root; 如果为(ALL)或者(ALL:ALL), 表示能够提权到(任意用户:任意用户组)。 请注意，&quot;字段3&quot;如果没省略,必须使用( )双括号包含起来。这样才能区分是省略了&quot;字段3&quot;还是省略了&quot;字段4&quot;。 &quot;字段4&quot;的可能取值是NOPASSWD:。请注意NOPASSWD后面带有冒号:。表示执行sudo时可以不需要输入密码。比如: 表示: 普通用户 max 可以在任何主机上, 通过sudo执行/bin/useradd命令, 并且不需要输入密码. 又比如: 表示: 普通用户 peter 可以在任何主机上, 通过sudo执行任何命令, 并且不需要输入密码。 &quot;字段5&quot;是使用逗号分开一系列命令,这些命令就是授权给用户的操作; ALL表示允许所有操作。 你可能已经注意到了, 命令都是使用绝对路径, 这是为了避免目录下有同名命令被执行，从而造成安全隐患。 如果你将授权写成如下安全性欠妥的格式: 那么用户就有可能创建一个他自己的程序, 也命名为userad, 然后放在它的本地路径中, 如此一来他就能够使用root来执行这个&quot;名为useradd的程序&quot;。这是相当危险的! 命令的绝对路径可通过which指令查看到: 比如which useradd可以查看到命令useradd的绝对路径: /usr/sbin/useradd 公式还要扩充 例子1: 表示: 用户papi能在所有可能出现的主机上, 提权到root下执行/bin/chown, 不必输入密码; 但运行/usr/sbin/useradd 命令时需要密码. 这是因为NOPASSWD:只影响了其后的第一个命令: 命令1. 上面给出的公式只是简化版，完整的公式如下: 在具有sudo操作的用户下, 执行sudo -l可以查看到该用户被允许和被禁止运行的命令. 通配符和取消命令 例子2: 用例子2来说明通配符*的用法, 以及命令前面加上!号表示取消该命令。 该例子的意思是: 用户papi在所有可能出现的主机上, 能够运行目录/usr/sbin和/sbin下所有的程序, 但fdisk除外. 开始编辑 “你讲了这么多,但是在实践中,我去编辑/etc/sudoers文件，系统提示我没权限啊，怎么办?” 这是因为/etc/sudoers的内容如此敏感，以至于该文件是只读的。所以，编辑该文件前，请确认清楚你知道自己正在做什么。 强烈建议通过visudo命令来修改该文件，通过visudo修改，如果配置出错，会有提示。 不过，系统文档推荐的做法，不是直接修改/etc/sudoers文件，而是将修改写在/etc/sudoers.d/目录下的文件中。 如果使用这种方式修改sudoers，需要在/etc/sudoers文件的最后行，加上#includedir /etc/sudoers.d一行(默认已有): 注意了，这里的指令#includedir是一个整体, 前面的#号不能丢，并非注释，也不能在#号后有空格。 任何在/etc/sudoers.d/目录下，不以~号结尾的文件和不包含.号的文件，都会被解析成/etc/sudoers的内容。 文档中是这么说的: 其他小知识 输入密码时有反馈 当使用sudo后输入密码，并不会显示任何东西 —— 甚至连常规的星号都没有。有个办法可以解决该问题。 打开/etc/sudoers文件找到下述一行: 修改成: 修改sudo会话时间 如果你经常使用sudo 命令，你肯定注意到过当你成功输入一次密码后，可以不用再输入密码就可以运行几次sudo命令。 但是一段时间后，sudo 命令会再次要求你输入密码。默认是15分钟，该时间可以调整。添加timestamp_timeout=分钟数即可。 时间以分钟为单位，-1表示永不过期，但强烈不推荐。 比如我希望将时间延长到1小时，还是打开/etc/sudoers文件找到下述一行: 修改成: 参考URL：https://segmentfault.com/a/1190000007394449 ","link":"https://hzqyihui.github.io/post/linux-etc-sudoers-wen-jian-shuo-ming/"},{"title":"Linux 用户，用户组相关命令","content":"用户，用户组相关： 对某个命令，不懂，可以直接使用 命令 --help 来查看帮助信息，如： ·useradd --help· 添加用户： 之后修改密码： 如果只是上面的命令，最后通过用户名和密码登录了系统，会发现该用户登录后， 用户名，主机名的位置，只有一个$符号， 而且按退格键，也不行，这就代表着没有给该用户指定shell，用户不能正常使用。 可通过： 或者直接在添加用户的时候，直接指定： 可以显式的指定某个shell， 同样也可以使用默认配置， 在 可以显式的指定某个shell， 同样也可以使用默认配置， 在 该配置只适用于 adduser 命令， adduser 不同于 useradd， 前者类似一个命令集合，一个shell脚本，会帮你配置，路径，shell，提示你输入密码这些的。而 useradd 是一个基础命令。 ","link":"https://hzqyihui.github.io/post/linux-yong-hu-yong-hu-zu-xiang-guan-ming-ling/"},{"title":"ln命令：对文件做链接，类似Windows的快捷方式","content":"ln命令： 对文件做链接，类似Windows的快捷方式 在很多情况下都会用到 ln 命令，作用在于基于原来的文件，创建一个链接，无论是软链接还是硬链接。 语法： 1.1 软链接：创建一个源文件的镜像，不占用空间，且随着源文件变动而变动。 该命令，必须把源文件和目标文件都写上具体的路径名字，不能写相对路径。 1.2 硬链接：创建一个源文件一模一样的文件，占用空间，且随着源文件变动而变动。 该命令，必须把源文件和目标文件都写上具体的路径名字，不能写相对路径。 ","link":"https://hzqyihui.github.io/post/ln-ming-ling-dui-wen-jian-zuo-lian-jie-lei-si-windows-de-kuai-jie-fang-shi/"},{"title":"df","content":"df: //查看系统中文件的使用情况 //查看当前目录下各个文件及目录占用空间大小 ","link":"https://hzqyihui.github.io/post/df/"},{"title":"chmod：","content":"chmod： 1.将文件 file1.txt 设为所有人皆可读取 : 2.将文件 file1.txt 设为所有人皆可读取 : 3.将文件 file1.txt 与 file2.txt 设为该文件拥有者，与其所属同一个群体者可写入，但其他以外的人则不可写入 : 4.将 ex1.py 设定为只有该文件拥有者可以执行 : chmod -R a+r * 改变用户组： chown -R Administrator:Administrator file chmod 777 file chmod abc file 其中a,b,c各为一个数字，分别表示User、Group、及Other的权限。 r=4，w=2，x=1 若要rwx属性则4+2+1=7； 若要rw-属性则4+2=6； 若要r-x属性则4+1=5。 chmod a=rwx file 和 chmod 777 file 效果相同 chmod ug=rwx,o=x file 和 chmod 771 file ","link":"https://hzqyihui.github.io/post/chmod/"},{"title":"用户埋点处理，用户行为分析技术：","content":"用户埋点处理，用户行为分析技术： 通常情况下，由于 用户埋点需要向后端发送参数，常见的方法是js 通过 ajax向后端发送相应参数，但是问题就在于， ajax不能跨域， 而如果再去配置后端，前端的允许跨域，就显得麻烦了。 一般这种方法可把 后端伪装成 一个图像， 比如 gif, 然后在链接上 带上参数，后端获取到进行相应操作 就好了 。 数据收集脚本（ga.js）被请求后会被执行，这个脚本一般要做如下几件事： （1）通过浏览器内置javascript对象收集信息，如页面title（通过document.title）、referrer（上一跳url，通过document.referrer）、用户显示器分辨率（通过windows.screen）、cookie信息（通过document.cookie）等等一些信息。 （2）解析_gaq收集配置信息。这里面可能会包括用户自定义的事件跟踪、业务数据（如电子商务网站的商品编号等）等。 （3）将上面两步收集的数据按预定义格式解析并拼接。 （4）请求一个后端脚本，将信息放在http request参数中携带给后端脚本。 这里唯一的问题是步骤4，javascript请求后端脚本常用的方法是ajax，但是ajax是不能跨域请求的。这里ga.js在被统计网站的域内执行，而后端脚本在另外的域（GA的后端统计脚本是http://www.google-analytics.com/__utm.gif），ajax行不通。一种通用的方法是js脚本创建一个Image对象，将Image对象的src属性指向后端脚本并携带参数，此时即实现了跨域请求后端。这也是后端脚本为什么通常伪装成gif文件的原因。通过http抓包可以看到ga.js对__utm.gif的请求： ","link":"https://hzqyihui.github.io/post/yong-hu-mai-dian-chu-li-yong-hu-xing-wei-fen-xi-ji-zhu/"},{"title":"PHP SPL扩展库","content":"一、__autoload 这是一个自动加载函数，在PHP5中，当我们实例化一个未定义的类时，就会触发此函数。看下面例子： ··· ./myClass.php <?php class myClass { public function __construct() { echo \"myClass init'ed successfuly!!!\"; } } ?> ./index.php <?php // we've writen this code where we need function __autoload($classname) { $filename = \"./\". $classname .\".php\"; include_once($filename); } // we've called a class *** $obj = new myClass(); ?> ··· 从上面能看到这是两个文件，下面的index.php 中，new了个 myClass类，但是明显本文件不存在，现在就会自动调用 __autoload函数，并 把 “myClass”这个类名字符串 直接作为参数传给__autoload， 此时自动加载函数内部就可以引入该文件了，引入后就正常初始化该类了。 该函数在PHP 7.2.0后被废弃了。 二：spl_autoload_register spl_autoload_register 可以将 函数自动注册，也就是说，当PHP文件内访问了一个不存在的类时，会自动去调用该函数，然后执行该函数内部的函数，看起来和 __autoload的作用是一样的。 但是其实spl_autoload_register 这个函数功能更强大， __autoload的参数 仅仅是一个函数名，这是定死的。并且只能声明一次， 使用了__autoload后，就不能再次使用该函数了。 请注意：一个项目中只能有一个__autoload， 如果在PHP在执行过程中遇到两个__autoload会直接报错的。 很明显，__autoload无法满足要求， 所以就有了SPL扩展，spl_autoload_register接受函数名或闭包，或数组作为参数，在闭包内部，即可引入对应的文件了。并且spl_autoload_register可以注册一个 自动加载队列， 先注册的，先调用。 比如说我使用： 可以结合require_once一起使用。如： 三：相关的其他SPL函数。 spl_autoload_call， 该函数是需要用户显示调用所有已注册的 __autoload函数的。 作用在 spl_autoload_register之后。 传入函数名字即可。即可手动引入文件了。 spl_autoload_functions， 可以获取到所有已经注册的__autoload函数， 也是作用在 spl_autoload_register之后的。 spl_autoload_extensions， 注册并返回spl_autoload函数使用的默认文件扩展名， 但是此接口和spl_autoload函数，用处不大。spl_autoload 是__autoload的默认实现，意思就是spl_autoload对__autoload进行了又一次封装，在默认情况下，本函数先将类名转换成小写，再在小写的类名后加上 .inc 或 .php 的扩展名作为文件名，然后在所有的包含路径(include paths)中检查是否存在该文件。 __autoload 函数是用来处理自动加载的函数，在 PHP 找不到指定类时就会去调用自动加载类，加载所需要的类。 __autoload 只是一个抽象定义，实现（实现就是定义如何加载，加载的规则是什么，加载的文件是什么等等）是交给用户的，而 spl_autoload 则是 SPL 所定义的 __autoload 一种实现。spl_autoload 函数所实现的加载规则就是去 include paths 中查找对于的类。spl_autoload 遵循是是 psr-0 的载入规则，而 include paths 就是载入时被查询的路径。 其他自己实现的 __autoload 类都可以通过 spl_autoload_register 进行注册，注册之后就可以在需要类时自动调用被注册的方法进行加载了。 spl_autoload 也是 __autoload 的一种实现，按理也是需要注册的，只不过因为是内部的默认实现，所有已经自动注册在 PHP 里了。 spl_autoload 如今来看并没有太多用处，应该是因为历史问题残留在 PHP 中的，目前绝大多数程序都没有使用 spl_autoload 去做自动加载，因为它的规则已经定死，并不适合衍生一些功能。 因为 PHP 只有一个自动加载方法，所以 SPL 的 spl_autoload 和 spl_autoload_register 要争抢这个方法，所以在 SPL 的 C 实现中，用了好多折衷的办法。在没有使用 spl_autoload_register 注册任何自定的自动加载函数时， PHP 的自动加载方法是挂在 spl_autoload 下的，而 spl_autoload_register 注册了自动加载函数后，PHP 的自动加载方法是挂在 spl_autoload_call 这个方法下的，而 spl_autoload 也会成为一个备选项进入 spl_autoload_register 的自动加载队列。 四：如果要自己实现MVC框架，使可以使用用的一段代码。 五： spl_auto_extension用法： 六：应用：比如这次做的腾讯云的SDK，这里即是只引入一个文件，这个文件中就是自动加载函数，后面就可直接使用命名空间了。 引入后，自动加载函数被注册，在使用命名空间的时候，遇到没引入的类，就会自动去加载了。 七： Composer 自动加载讲解。 https://segmentfault.com/a/1190000014948542 ","link":"https://hzqyihui.github.io/post/php-spl-kuo-zhan-ku/"},{"title":"Beanstalked学习笔记：","content":"1、Beanstalkd设计里面的核心概念： 2、job 的生命周期 当producer直接put一个job时，job就处于READY状态， 等待consumer来处理，如果选择延迟put，job就先到DELAYED状态，等待时间过后才迁移到READY状态。 consumer获取了当前READY的job后，该job的状态就迁移到RESERVED，这样其他的consumer就不能再操作该job。 当consumer完成该job后，可以选择delete, release或者bury操作；delete之后，job从系统消亡，之后不能再获取；release操作可以重新把该job状态迁移回READY（也可以延迟该状态迁移操作），使其他的consumer可以继续获取和执行该job； 有意思的是bury操作，可以把该job休眠，等到需要的时候，再将休眠的job kick回READY状态，也可以delete BURIED状态的job。 正是有这些有趣的操作和状态，才可以基于此做出很多意思的应用，比如要实现一个循环队列，就可以将RESERVED状态的job休眠掉，等没有READY状态的job时再将BURIED状态的job一次性kick回READY状态。 3、 beanstalkd 在 Laravel中的应用， 实际上在Laravel中对队列的操作，是已经封装好的。我们会发现在 Laravel中，使用 dispatch之类的函数，即可把一个任务 给放到队列中了。然后监听执行。而实际上，如果操作原生的队列， 如果不去手动 delete， 是不会把队列里的任务给删除的。 Producer 示例：向队列中添加job Consumer 示例：从队列中取出job 检查服务状态 获取某一 tube 的数据 4.发送任务： 5.处理任务： ","link":"https://hzqyihui.github.io/post/beanstalked-xue-xi-bi-ji/"},{"title":"tar命令：","content":"tar命令： 常见解压/压缩命令 tar （注：tar是打包，不是压缩！） .gz .tar.gz 和 .tgz .bz2 .tar.bz2 .bz .tar.bz ","link":"https://hzqyihui.github.io/post/tar-ming-ling/"},{"title":"GIT 原理   ：","content":"Git 是一套内容寻址文件系统。很不错。不过这是什么意思呢？ 这种说法的意思是，Git 从核心上来看不过是简单地存储键值对（key-value）。它允许插入任意类型的内容，并会返回一个键值，通过该键值可以在任何时候再取出该内容。 我们都知道当我们初始化一个仓库的时候，也就是执行以下命令后，文件夹内会生成一个.git文件夹， 内部会包含，以下文件夹。 hooks //钩子文件夹，内部文件实际上就是一些特定时间触发的shell脚本，我们可以简单的做一个部署系统，每次提交特定tag的时候，则部署最新的代码到服务器。 objects //真正的内容存放的文件夹，下面重点讲下这里。 refs //refs目录存放了各个分支（包括各个远端和本地的HEAD）所指向的commit对象的指针（引用），也就是对应的sha-1值；同时还包括stash的最新sha-1值 config //git配置信息，包括用户名，email，remote repository的地址，本地branch和remote branch的follow关系 HEAD //存放的是一个具体的路径，也就是refs文件夹下的某个具体分支。意义：指向当前的工作分支。项目中的HEAD 是指向当前 commit 的引用，它具有唯一性，每个仓库中只有一个 HEAD。在每次提交时它都会自动向前移动到最新 的 commit index //存放的索引文件，可使用 git ls-files --stage 查看。应该zlib加密后的，PHP可使用gzdeflate()函数 这是objects文件夹，可以看到都是些数字和字符，实际上就是十六进制数。 下图是进入00文件夹后所有文件。 认识下GIT对象： blob对象， tree对象， commit对象 1.创建blob对象 下面我们直接上底层命令， 运行此命令后，会在 .git/objects 文件夹下生成一个 两个字符 的文件夹，文件夹内部文件即类似上图中文件一样。 分解命令： 所以实际上我们看到的，objects 文件夹下的内容，文件名实际上是 hash 值。文件夹是40个字符的前两个（拥有相同前2位的hash值会被分配到同一个文件夹中）， 具体文件名则是后面38个字符。使用hash值的原因就在于，位数够多，并且hash值唯一，一点小变化，都会生成新的hash值，和md5算法是一样的道理。 注意：此hash值就像是GIT的指针，能唯一对应某一个具体的内容或提交，hash值作为寻址作用，不作为内容存储用，具体的文件内容存储方式是GIT更底层的存储方式决定。（sha-1和md5一样，均是不可逆的） 通过Linux find 命令查看所有已存储的hash文件： 通过 cat-file 命令可以将数据内容取回。该命令是查看 Git 对象的瑞士军刀。传入 -p 参数可以让该命令输出数据内容的类型： 通过 hash-object 命令，会把每一个文件的内容都给记录下来， 以此生成一个blob对象。可通过以下命令查看对象的类型 在实际项目过程中，不会这么简单，因为我们每次提交都是一个多文件的提交。很少的时候是单文件的，那此时Git就不是单单存储一个 blob对象了，而是 tree对象， tree对象，见名知意，就是一个树对象，类似于操作系统目录，tree的分支，可能还是tree，也可能是blob，这就看实际的场景了。 对象存储方法： GIT使用 zlib 库 的 deflate方法对数据内容进行压缩，但内容为 &quot;blob 字符串长度+空字节+字符串本身&quot;； 如： 2.创建tree对象 上面说的创建blob对象，仅仅只是对某一个文件进行的计算与存储，而我们实际项目中，可能每一次操作都是好几个，甚至十几个文件一起，那如何才能把他们组织到一起，这就是 tree 对象的作用了。 要创建tree对象，需要使用 update-index，write-tree 命令： --cacheinfo 会从已存在的数据库（Object）中取得对应的内容给添加到索引中。 实际生产中，一般情况下，会把末尾文件夹中的所有修改文件创建，blob对象，再对该文件夹（也就是所有的blob对象整体）进行write-tree的操作，得到一个tree对象，反复进行此操作，最后得到多个tree对象和多个blob对象。 如上所说，若需要对某个存在三级文件夹的二级文件夹进行write-tree操作， 在把三级文件夹下的所有修改文件生成blob后，进行整体tree对象化，之后再与二级文件夹同级的文件夹和文件进行相同操作。此时就需要用到： read-tree 命令。如： 3.创建commit对象 平时我们都是用** git commit -m &quot;xxxx&quot;** 提交了信息， 在这之前，会暂存相关文件的改动， 在提交后，会生成对应的tree对象，返回tree所对应的 sha-1值， 再进行一次 commit-tree 操作，最后会把刚保存的tree对象所对应的sha-1值 赋值给 commit-tree， 即生成了一个commit 对象。用法： 通过 git cat-file -p f7bc39001ff6cb183022234c94aa61ddedee44e0 得到： 我们还可以给某一个commit对象指定它的父commit对象： 通过 git cat-file -p 42e08b70c341b7e60944de6dffc342b77f94f6e4得到： 想要查看我们使用管道命令生成的log记录： git log --stat 42e08b70c341b7e60944de6dffc342b77f94f6e4 ,得到： 从上面的用法可以得到， git commit-tree 生成的 commit对象，只会包含 tree对象，参数选项中没有可以指定blob对象的参数。 如下：在测试时，强制使用blob对象的 sha-1值，会出现报错现象。 4.应用 以上基本上就可概括平时使用git add 和 git commit 命令时GIT的工作。 保存已修改文件成blob格式对象： git hash-object -w 各个文件 更新索引： git update-index --add 各个文件名 或者 git update-index --add --cacheinfo mode sha-1 文件名 或者 git read-tree --prefix=test sha-1(某个tree的sha-1) ，作用在于把某个tree读入索引中 创建树对象: git write-tree 最后创建commit对象： git commit-tree sha-1 -m &quot;提交信息&quot; 或者 echo &quot;提交信息&quot; | git commit-tree sha-1 -p 父级sha-1 4.1 git add 平时我们在使用的时候，使用 git add c.txt 后，把 c.txt 放入了暂存区， 而实际上此时已经生成了blob对象，并保存了相应的sha-1值命名的文件，同时添加到了索引文件中；之后当我们修改了之前添加到暂存区的文件并使用 git status 查看状态的时候，GIT会再对文件进行一次 hash运算，如果发现和已存在与索引中的内容产生了变化（sha-1值不同），则又会呈现出一个 Modify 状态。 通过以下命令可查看到 .git/index 文件中的内容，其中存放了每一个被追踪的文件，对应的blob对象最新的sha-1值， 通过这里即可很直接的判断出哪个文件是否被修改，哪些没有被追踪了。 4.2 git diff 同上， 使用 git diff后， 会把文件的差异给列出来，而对比对象即是 索引中的内容，并不是HEAD指向的内容。 当对某文件执行了 git add 后，之后再进行修改，再使用git diff 查看区别， 你会发现已经存在区别了。也就是说，git diff 实际上是把当前文件与索引中的文件进行比较（通过sha-1值比较），当有不同的情况，则列出对应的改变。 4.3 git status 使用 git status 后，GIT会对所有文件进行sha-1值计算，若计算到与前面讲到的 索引中得对应文件的sha-1值不同了，则代表有所改动，则标记为 Modify，若发现索引中不存在对应文件的sha-1值， 则标记为 Untracked files。 4.4 git branch 分支名 该命令会生成一个新分支，也就是在 .git/refs/heads里面生成一个新的文件，文件名为分支名，如果有前缀feature之类的。则feature是文件夹名，其内是文件名。文件内容为当前的 commit 对象对应的sha-1值。所以实际上分支，也是一个 commit 对象的引用。只是在GIT中专门有文件记录了分支名和指向。我们甚至可以通过创建文件的方式，直接创建branch。 4.5 git checkout 某分支 当使用 git checkout 的时候， GIT内部实际上就是把当前的HEAD指针给指向了另一个分支，而实际上也就是把 .git/HEAD 文件内容修改为切换的分支，而 .git/HEAD 内容指向的就是 .git/refs/heads中的分支，此文件内容又是一个 commit 对象的 sha-1值，所以也就间接指向了某个具体的 commit对象了， 从这个commit对象可得到它的父级对象，依次类推，即可得到完整的代码。 有时候，我们在使用PHPStorm的时候，会用到&quot;Annotate&quot;， 就是查看本文件的GIT提交记录，还会查看某个提交下以前的版本的文件，看具体是修改了啥。&quot;Amnotate previous revision&quot;，实际上就是做了 4.6 git commit -m &quot;提交信息&quot; 见如上信息。 4.7 git log 使用该命令后，去 .git/logs 下寻找当前分支对应的文件名，文件中的内容即为每一次提交的信息。 4.8 git push 使用git push 是把当前的分支上传到远程仓库，并把这个 branch 的路径上的所有 commits 也一并上传。 我认为实际就是修改了.git中的文件，因为这些文件里实际上就已经包含了压缩后的代码，等你切换分支的时候，GIT会根据这些内容把代码给检索出来。 4.9 git tag [version name] 使用 git tag 实际上和 git branch 类似，branch 是指向某一个commit的指针，但是branch会随着每次提交而移动， 但是tag不会， 当打了tag后， 那这个 tag 对应的commit对象指针就固定了，不会移动了。它和 git branch 一样，都不会产生blob或 tree 对象， git tag 只会在 .git/refs/tag 下生成一个 tag名的文件，内容为指向当前commit的sha-1 4.10 git stash 使用git stash 实际上是创建了一个新的commit对象，为什么这么说呢？在 .git/refs 目录下，当第一次stash后，会生成一个 stash文件， 内容即为一个sha-1值， 通过 git cat-file -p查看到具体内容为一个 commit对象的内容， 还能看到其有两个 父级 commit， 一个是前一个 git commit 的sha-1值， 一个是执行stash后，新生成的commit。最后把这两个commit对象作为父亲，再生成一个commit对象存放于stash文件中。 疑问： git commit-tree的时候，是怎么指定父级commit对象的，以什么为参考，才能指定对应的父级commit对象。我从平时工单的log记录来看，有些commit对象有两个父级commit对象（可能是合并操作的时候自动生成的commit对象），但不一定就是前一个commit。(git stash 有两个父级对象) 我们都知道git stash 存的是一个栈的结构，但是 .git/refs/stash 文件里 只有一个sha-1值，只对应一个commit对象，我查看了commit对象的具体内容，他的两个父亲均不是我前一个创建的stash对应的commit对象，不知道这个栈的结构怎么来的。 看某项目的树结构，会发现，每一个commit对象内部对应的 tree，都是一整个项目，而不是某一个文件或者某几个文件夹，这就解决了我的疑惑， 每次只需 git update-index --add 后，我想新创建一个 tree对象， tree对象内部一直会存在之前加入index中的blob对象。 那么GIT实际上就是每一个commit，都应该能从tree和 p-tree上追溯到整个项目文件。 在手动创建分支的过程中，发现在执行 git init后，看起来你是在master分支， 但是实际执行 git branch，看不到任何输出，这说明在这个时候实际上master分支是没有创建的，必须要有第一次提交后，master分支才会创建，因为只有这样 ， .git/refs/head/master 文件中才有可写的 commit 对象的 sha-1值。 需要注意：对commit对象的跟踪，commit对象能跟踪到具体哪一次修改，改了哪些具体文件，通过对commit的切换，就能找到某个时间点的文件记录了。GIT每次提交文件，实际上都是提交的整个文件，而不仅仅是修改的部分。所以当我们执行一些回退操作的时候能回到某个时间点的文件，即直接指定某个commit对象，查到commit对象中包含的各类tree对象和blob对象，把这些对象中压缩内容给取出来覆盖当前的同级，同名文件即可；同时新增的，给删除了。 ","link":"https://hzqyihui.github.io/post/git-yuan-li/"},{"title":"tmux使用","content":"tmux 工具使用： 新建会话 下面我们就来新建一个会话。可以使用 new 命令新建会话，并且该命令允许以参数的形式传递一个会话名。我的建议是在新建时要提供一个会话名以便于日后管理。(-s 可以想成 server，意思是新建一个server) 新建会话但并不指定名字 (不推荐这样做) 接入一个之前的会话 既然我们已经创建了多个带有名称的会话，那么就可以随时接入了，有几种方法可以实现接入会话： 可以简单地输入 tmux a 命令，这样可以接入第一个可用的会话：（a 可以 当成是 access(接入，进入)的缩写） 或者可以通过参数指定一个想接入的会话： 从会话中断开 可以使用 detach 命令断开已有的会话（因此才会有稍后重新接入会话这么一说）。 也可以使用快捷键断开会话： 关闭会话 要关闭会话的话，可以使用如下的命令，该命令和接入会话时所使用的命令很像： 提示：关闭窗口时也可以使用类似的命令，只不过要把 kill-session 换成 kill-window。另外，还可以使用 tmux killall 同时关闭 tmux。 从零开始编译自己的Linux：网址： https://linuxstory.org/linux-from-scratch-8-0-released/ 学习tmux 网址； https://www.jianshu.com/p/fd3bbdba9dc9 http://mingxinglai.com/cn/2012/09/tmux/ ","link":"https://hzqyihui.github.io/post/tmux-shi-yong/"},{"title":"Debian9学习笔记：","content":"Debian9学习笔记： 安装命令笔记： http://man.linuxde.net/yum 第一次安装Debian：需要做的事： SSH设置可登录，在/etc/ssh文件夹下， 把sshd_config文件的 PermitRootLogin 一行，改为 PermitRootLogin yes， 再 service ssh restart重启服务，即可使用账户密码登录。 由于使用的虚拟机，可在VirtualBox中设置端口转发， 如本机127.0.0.1的22222端口对应虚拟机内部10.0.2.15的22端口，即可正常连接。 由于新安装的Debian很多命令不能用，需要更新/etc/apt/source.list中的源。 阿里云源： Debian 镜像 简介 Debian GNU/Linux ，是一个操作系统及自由软件的发行版，由一群自愿付出时间和精力的用户来维护并更新。它附带了超过 59000 个软件包，这些预先编译好的软件被打包成一种良好的格式以便于用户安装和使用。 下载地址: https://mirrors.aliyun.com/debian/ https://mirrors.aliyun.com/debian-archive/ 配置方法 debian 7.x (wheezy) 编辑/etc/apt/sources.list文件(需要使用sudo), 在文件最前面添加以下条目(操作前请做好相应备份) debian 8.x (jessie) 编辑/etc/apt/sources.list文件(需要使用sudo), 在文件最前面添加以下条目(操作前请做好相应备份) debian 9.x (stretch) 编辑/etc/apt/sources.list文件(需要使用sudo), 在文件最前面添加以下条目(操作前请做好相应备份) debian 10.x (buster) 编辑/etc/apt/sources.list文件(需要使用sudo), 在文件最前面添加以下条目(操作前请做好相应备份) debian 11.x (bullseye) 编辑/etc/apt/sources.list文件(需要使用sudo), 在文件最前面添加以下条目(操作前请做好相应备份) 由于新安装的debian，导致很多命令都没有，则可以直接安装，apt-get install vim，apt-get install ifconfig 若使用非root用户，去安装vim之类的工具会提示权限不足，导致不允许，则可以去加入sudoers.is not in the sudoers file 在此之前可能压根就不存在这个文件，这个时候就需要去安装 sudo 才可用。apt-get install sudo 此时可以把当前用户加入到sudoers组里去，则可以 直接 去编辑 /etc/sudoers 文件， 加入：root ALL=(ALL:ALL) ALL 这种类似的 ，替换用户名保存即可，非root用户使用sudo 接下来就可以正式进入安装Docker的环节了。 由于是在VirtualBox中安装的debian， 故需要添加一些额外的增强功能 6.1 首先，在某个虚拟机实例的设置中，添加控制器， VirtualBox Guest Additions.iso， 在这里挂载了后，就进入Debian中。执行： 后面的是想要挂载的目录 6.2， 挂载后，进入到 home/max/www/ 目录中， 执行： 之后就可以重启虚拟机，可正常执行挂载共享文件夹了。 若一开始选择的中文语言，希望后期能改到英文去，可进行： 将下面两行内容 然后重启系统，显示正常了。 若需要定制一些命令的别名（此方法适合对该用户永久生效），可以在自己的家目录创建.bash_aliases文件。 有个工具zsh很好用，比原始的sh和bash 要好用，故提供安装： 安装好后，更换本机的shell，使用： 在安装了zsh后， 给用户指定shell的过程中， 因为直接输入的， //这样也能把zsh 写入到 /etc/passwd文件中，但是当时遇到个问题，当想使用该用户登录的时候，无论怎么样操作，都登录不上，用户名，密码都是对的。 后面发现就是因为在换 shell的过程中， 换错了。 zsh必须指定绝对路径，不然就会导致登录不进去。 可参考： https://mengkang.net/153.html 更换后，重新登录终端即可。虽然zsh比较好用，但是zsh还有更好用的主题，最出名的就是oh-my-zsh 安装完成后，可以更改默认的zsh主题，再 可能存在下不下来的 情况，此时需要把install.sh 在本地进入打开后， 把内容粘贴到 linux 中，再手动执行， 又可能存在 Git 报错，又被墙了，此时 设置 不使用代理。 显示本机ip， 导入某个命令到环境变量中去： github520 帮助随时能访问 github 问题： 在下载某些依赖或者安装包后发现终端乱序，输入命令时按退格键不能回退反而向后加空格；并且某些快捷键无法使用等问题； 原因 终端依赖的包ncurses-base被删除； 解决办法 终端输入： ","link":"https://hzqyihui.github.io/post/debian9-xue-xi-bi-ji/"},{"title":"Docker学习笔记","content":"Docker学习笔记： docker中文网： https://docs.docker-cn.com/ 检查docker的版本命令： 查看docker更多细节信息的命令： 查看本机所有docker镜像 查看本机所有docker容器 获取docker帮助 6.删除docker镜像 列出本机正在运行的容器： 8.运行容器 此命令，每次运行都会生成一个容器，可用在第一次依赖镜像创建容器的时候，因为该命令会配置一些参数，生成的容器即带了所配置的参数了。 创建后，以后要运行，直接可以通过容器ID来运行了，参考下面的命令。 开启已有的容器 终止已运行的容器 由于官方原镜像很慢，所以若需要更快的速度可以拉取docker镜像，则可以： 12.docker实用命令集合： mysql 测试： Dockerfile编写：学习链接 使用Docker Commit创建镜像 14.1 首先需要进入某个容器内， Docker网络： Docker容器有5种网络模式， 在使用docker run创建docker容器时，可以用--net选项指定容器的网络模式，Docker有以下5种网络模式： bridge模式 使用docker run --net=bridge指定，bridge模式是Docker默认的网络设置，此模式会为每一个容器分配Network Namespace、设置IP等，并将一个主机上的Docker容器连接到一个虚拟网桥上。 此模式与外界通信使用NAT协议，增加了通讯的复杂性，在复杂场景下使用会有诸多限制。 route -n 查看 IP routing tables; iptables -t nat -L -n 查看iptables rules. host模式 使用docker run --net=host指定，这种模式Docker Server将不为Docker容器创建网络协议栈，即不会创建独立的network namespace,Docker容器中的进程处于宿主机的网络环境中，相当于Docker容器的宿主机共用同一个network namespace,使用宿主机的网卡、IP、端口等信息。此模式没有网络隔离性，同时会引起网络资源的竞争与冲突。 container模式 使用docker run --net=container:othercontainer_name指定，这种模式与host模式相似，指定新创建的容器和已经存在的某个容器共享同一个network namespace, 以下两种模式都共享network namespace,区别就在于host模与宿主机共享，而container模式与某个存在的容器共享。 在container模式下，两个容器的进程可以通过lo回环网络设备通讯，增加了容器间通讯的便利性和效率。container模式的应用场景就在于可以将一个应用的多个组件放在不同的容器趾，这些 容器配成container模式的网络，这样它们可以作为一个整体对外提供服务。同时，这种模式也降低了容器间的隔离性。 docker run -it --name helloworld busybox sh docker run -it --name helloword-con --net=container:helloword busybox sh 4.none模式 使用docker run --net=none指定，在这种模式下，Docker容器拥有自己的Network Namespace，但是，并不为Docker容器进行任何网络配置。也就是说，这个Docker容器没有网卡、IP、路由等信息。需要我们自己为Docker容器添加网卡、配置IP等。这种模式如果不进行特定的配置是无法正常使用的，但它也给了用户最大的自由度来自定义容器的网络环境。 overlay模式 overlay网络特点： 跨主机通讯 无需做端口映射 无需担心IP冲突 服务发现与k/v存储: etcd, consul config1.sh This transaction has been declined 返回有关网络的信息： docker-compose up 与 docker run不同， docker-compose up不会新创建容器，除非手动删除容器，不然container id每次都相同，不同于docker run 每次都要根据镜像重新生成一个实例。 docker-compose.yml的写法 docker save ： 将置顶镜像保存成tar归档文件。 docker 镜像导入导出有两种方法： 一种是使用 save 和 load 命令 使用例子如下： 一种是使用 export 和 import 命令 使用例子如下： export 和 import 导出的是一个容器的快照, 不是镜像本身, 也就是说没有 layer。 你的 dockerfile 里的 workdir, entrypoint 之类的所有东西都会丢失，commit 过的话也会丢失。 快照文件将丢弃所有的历史记录和元数据信息（即仅保存容器当时的快照状态），而镜像存储文件将保存完整记录，体积也更大。 如果需要对已经生成的一个容器进行修改启动策略，可以使用 update 命令。 ","link":"https://hzqyihui.github.io/post/docker-xue-xi-bi-ji/"},{"title":"在Debian9安装Docker","content":"Docker： 镜像（Image）； 容器（Container）； 仓库（Repository）。 Debian系统，根据Docker官方教程来部署Docker 1.第一步： 去掉老旧版本的Docker， 若明确，系统内没有docker相关程序，也可不执行。 2.第二步：确保Linux内核大于 3.10 ，如何查看Linux内核，可用如下命令 若内核小于 3. 10 ，则可以升级内核，如需升级内核，可跳转：https://wiki.debian.org/HowToUpgradeKernel 3.第三步： 启用backports仓库，一般情况下首次安装完Debian后，source.list文件内的源都不够完整，推荐使用，中科大的源： 直接去 /etc/apt/source.list文件，粘贴以上内容，随后执行 由于我使用但是Debian9， 根据Docker官方描述：需要使用， Add Docker’s official GPG key: 此处可能会报错： 在网上查询：用此命令解决： 下面添加Docker仓库源 下面开始正式安装：（可根据第8 和 第9选择安装某个版本） 查找并列出docker-ce有哪些版本 接下来根据上面的结果选择安装的版本就好了。如：docker-ce=18.03.0.ce 安装完成查看安装的docker是否可用。 改名了会自动拉取 hello-world镜像，如果能显示正常的结果，则证明安装好了。接下来就是使用docker了。 建立docker开机启动 开启docker 13.默认情况下，docker 命令会使用Unix socket 与 Docker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑，一般 Linux 系统上不会直接使 用 root 用户。因此，更好地做法是将需要使用 docker 的用户加入 docker 用户组。 建立docker组： 所有的安装完成后，可以再安装docker-compose: docker-compose的作用是，将所有需要共同关联的服务绑定到一起，可以直接一起启动，也就是一键启动。无需自己再一个个去启动各个容器了。 你需要定义一个 YAML 格式的配置文件docker-compose.yml，写好多个容器之间的调用关系。 安装web所需各类镜像 可参考的文档地址 ： https://yeasy.gitbook.io/docker_practice/install/centos 17： 安装 Mysql5.7 Ubuntu 16.04+、Debian 8+、CentOS 7+ 目前主流 Linux 发行版均已使用 systemd 进行服务管理，这里介绍如何在使用 systemd 的 Linux 发行版中配置镜像加速器。 请首先执行以下命令，查看是否在 docker.service 文件中配置过镜像地址。 如果该命令有输出，那么请执行 $ systemctl cat docker 查看 ExecStart= 出现的位置，修改对应的文件内容去掉 --registry-mirror 参数及其值，并按接下来的步骤进行配置。 如果以上命令没有任何输出，那么就可以在 /etc/docker/daemon.json 中写入如下内容（如果文件不存在请新建该文件）： 注意，一定要保证该文件符合 json 规范，否则 Docker 将不能启动。 之后重新启动服务。 或者直接使用docker的官方shell，直接安装 ","link":"https://hzqyihui.github.io/post/zai-debian9-an-zhuang-docker/"},{"title":"Git学习笔记","content":"可供学习的链接： 内部原理： 可学习： 本地仓库(版本库) 版本库：工作区有一个隐藏目录.git，这个不算工作区，而是Git的版本库。 本地仓库(版本库)是什么 什么是版本库呢？版本库又名仓库，英文名repository，你可以简单理解成一个目录，这个目录里面的 所有文件都可以被Git管理起来，每个文件的修改、删除，Git都能跟踪，以便任何时刻都可以追踪历 史，或者在将来某个时刻可以“还原”。 暂存区 暂存区：英文叫stage, 或i ndex。一般存放在 &quot;.git目录下&quot; 下的i ndex文件（.git/index）中，所以我们 把暂存区有时也叫作索引（index）。 工作区 工作区：就是你在电脑里能看到的目录。 我对g it各分支的理解： 把分支归为三类： 1. 远程分支， 2. 本地远程分支（暂存提交后进入这 里）， 3.本地分支 当使用fetch, 或u pdate 时，会把远程分支的代码给更新到本地远程分支， 最后通过merge 把 本地远 程分支的代码给合并到本地分支。 同样，a dd,commit后进入本地远程分支， 最后push后再到远程分 支。 根据某个分支 创建新的分支： 以上命令指：以本地 master 分支为基准，创建 feature/test 分支并切换到该分支。 以上命令指： 以本地远端 master 分支为基准， 创建feature/test 分支，不切换 以上命令指：根据本地远端serverfix分支，新建一个本地serverfix分支，并保持跟踪关系。 拉取并合并代码： 以上指： 拉取所有origin分支的代码到本地远端分支。 如果加入具体分支名，则只更新具体的本地远 端分支 以上指： 当本地处于某一个分支时：执行以上命令后，就会把 本地远端分支的master代码，给合并到 当前分支，也可以合并 develop分支到本地分支。 以上命令不同于这个： 该命令：指的是把 本地m aster 分支合并到 当前分支来，上面的命令指的是把本地远端master分支 合 并到 当前分支， 本地远端分支 由 git fetch origin master 来保证最新 这里有三个分支概念： 本地分支， 本地远端分支， 远端分支。 本地分支指的就是开发人员所在的能 操作的分支； 本地远端分支，起到的是一个过渡的作用，可以作为合并远端分支的对象，也可以作为 创建本地分支的，代码基准。git fetch 命令获取到的代码更新就会更新到 本地远端分支，再由merge 操作来把本地远端分支的代码更新 合并到当前分支来。 删除分支（强制删除分支） 删除远端分支可用以下命令： 撤销操作： 以上命令指把 benchmarks.rb 文件从暂存状态恢复到未暂存的状态，但是之前修改的内容仍然存在， 不会丢失。这个仅仅只是撤销暂存区 该命令指的是：可以撤销到哪一个时间节点，对应的hash-id就好（--hard会丢失更改） 以上命令会把 未暂存的benchmarks.rb 文件 恢复到修改之前，会丢弃所有更改。 以上命令可以撤销，已提交的操作，之后重新编辑提交信息，再次commit（尽量少用）。 你在最后一条 commit 消息里有个笔误，已经执行了 git commit -m &quot;Fxies bug #42&quot;，但在 git push 之前你意识到消息应该是 “Fixes bug #42′′。 git commit --amend 会用一个新的 commit 更新并替换最近的 commit ，这个新的 commit 会把任何修 改内容和上一个 commit 的内容结合起来。如果当前没有提出任何修改，这个操作就只会把上次的 commit 消息重写一遍。 至于这几个参数： --mixed 意思是：不删除工作空间改动代码，撤销commit，并且撤销git add. 操作 这个为默认参数,git reset --mixed HEAD^ 和 git reset HEAD^ 效果是一样的。 --soft 不删除工作空间改动代码，撤销commit，不撤销git add . --hard 删除工作空间改动代码，撤销commit，撤销git add . 注意完成这个操作后，就恢复到了上一次的commit状态。 6. 删除文件 分段新增文件： 7. 建立关联 git本地新建一个分支后，必须要做远程分支关联。如果没有关联， git 会在下面的操作中提示你显示的 添加关联。关联目的是如果在本地分支下操作： git pull, git push ，不需要指定在命令行指定远程的分 支． 推送到远程分支后， 你只要没有显示指定，git pull 的时候，就会提示你。 解决办法是使用命令git branch --set-upstream 或者(git branch -u ) ;实例如下，其中debug为创建 的分支 上述命令：即将被GIT官方移除，官方推荐使用以下命令： 还可以使用：根据远端serverfix分支 新建一个本地serverfix分支，并保持跟踪关系、 如果想向同一个源名字， 添加不同的 push 地址，可以使用以下命令，目的在于每次push origin 的时 候， 就可以向两个地址，同时 Push 了。 当然也可以建立不同的源： 如下：新加了个 upstream 那么如何查看已经配置分支关联信息呢，通过下述三条命令均可： git branch -vv git remote show origin cat .git/config 推送本地分支到远端：以下命令仅为把当前分支推送到远端的test分支。相当于它说，“推送本地的 serverfix 分支，将其作为远程仓库的 serverfix 分支&quot; 如果想把当前test分支推送到远端另一个名字的分支，如test_2，则需要使用： 9. 查询是否需要更新代码。 9.1 若显示以下代码则说明当前分支的代码和远端对应的分支是相同提交，无需更新： 9.2 若显示以下代码，则说明当前分支落后于远端分支一个提交，提示信息已给出，可以使用Pull 来获 取对应的提交： 9.3 若显示以下代码，则说明之前还有一个未提交，需要提交上去（这种情况都很少，一般情况add 后，都会⻢上push） git储藏 ,储藏后，要恢复可使用： 查看日志信息 从图中可看出，这几个相关分支，在日志方面的动向，我当前的Head指针指向了我的开发分支，而我 现在的开发分支是和远端的develop， master是一样的。这说明我的分支还没有任何提交。而看到其 他分支都是在这几个hash-id的下方， 指的就是那几个分支落后于develop，如果是还没更新的代码， 则需要合并下最新的更新。 10.1 加对应参数：-p， 加了 -p后，就会显示每个提交的内容差异。 后面如果再加 -2(数字) 则，代 表查看两次提交的。 9.git 变基操作： 学习U RL： 整合分支最容易的方法是 merge 命令。 它会把两个分支的最新快照（C3 和 C4）以及二者最近的共同 祖先（C2）进行三方合并，合并的结果是生成一个新的快照（并提交）。 通过合并操作来整合分叉了的历史 其实，还有一种方法：你可以提取在 C4 中引入的补丁和修改，然后在 C3 的基础上应用一次。 在 Git 中，这种操作就叫做 变基。 你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支 上，就好像“重新播放”一样。 在上面这个例子中，运行： rebase master， 即把当前分支分支 experiment 分支修改的代码 变基到 master，即意味着 experiment 分支会把master分支的代码给合并过来，但是在log上，父节点为c3了，而 不是c 它的原理是首先找到这两个分支（即当前分支 experiment、变基操作的目标基底分支 master）的最近 共同祖先 C2，然后对比当前分支相对于该祖先的历次提交，提取相应的修改并存为临时文件，然后将 当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。（译注：写明了 commit id，以便理解，下同） Figure 37. 将 C4 中的修改变基到 C3 上 现在回到 master 分支，进行一次快进合并。 Figure 38. master 分支的快进合并 此时，C4' 指向的快照就和上面使用 merge 命令的例子中 C5 指向的快照一模一样了。 这两种整合方法 的最终结果没有任何区别，但是变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记 录时会发现，尽管实际的开发工作是并行的，但它们看上去就像是串行的一样，提交历史是一条直线 没有分叉。 一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个其他人 维护的项目贡献代码时。 在这种情况下，你首先在自己的分支里进行开发，当开发完成时你需要先将 你的代码变基到 origin/master 上，然后再向主项目提交修改。 这样的话，该项目的维护者就不再需 要进行整合工作，只需要快进合并便可。 请注意，无论是通过变基，还是通过三方合并，整合的最终结果所指向的快照始终是一样的，只不过 提交历史不同罢了。 变基是将一系列提交按照原有次序依次应用到另一分支上，而合并是把最终结果 合在一起。 13.找回丢失的文件 展示所有 tracked 的文件 展示所有 untracked 的文件 展示所有忽略的文件 强制删除 untracked 的文件 可以用来删除新建的文件。如果不指定文件文件名，则清空所有工作的 untracked 文件。clean 命 令，注意两点： clean 后，删除的文件无法找回 不会影响 tracked 的文件的改动，只会删除 untracked 的文件 强制删除 untracked 的目录 可以用来删除新建的目录，注意：这个命令也可以用来删除 untracked 的文件。详情⻅上一条 展示简化的 commit 历史 把某一个分支到导出成一个文件 还可以搜索字符串 获取最新的标签 1 4. GIT 原理 14. 1 底层原理： ","link":"https://hzqyihui.github.io/post/git-xue-xi-bi-ji/"}]}